├── README.md ├── app ├── code │ └── community │ │ └── Hackathon │ │ └── LoggerSentry │ │ ├── Helper │ │ └── Data.php │ │ ├── Model │ │ └── Sentry.php │ │ └── etc │ │ ├── config.xml │ │ └── system.xml └── etc │ └── modules │ └── Hackathon_LoggerSentry.xml ├── composer.json ├── lib └── sentry │ └── sentry │ ├── .gitattributes │ ├── .gitignore │ ├── .gitmodules │ ├── .php_cs │ ├── .scrutinizer.yml │ ├── .travis.yml │ ├── AUTHORS │ ├── CHANGELOG.md │ ├── LICENSE │ ├── Makefile │ ├── README.md │ ├── bin │ └── sentry │ ├── composer.json │ ├── lib │ └── Raven │ │ ├── Autoloader.php │ │ ├── Breadcrumbs.php │ │ ├── Breadcrumbs │ │ ├── ErrorHandler.php │ │ └── MonologHandler.php │ │ ├── Client.php │ │ ├── Compat.php │ │ ├── Context.php │ │ ├── CurlHandler.php │ │ ├── ErrorHandler.php │ │ ├── Exception.php │ │ ├── Processor.php │ │ ├── Processor │ │ ├── RemoveCookiesProcessor.php │ │ ├── RemoveHttpBodyProcessor.php │ │ ├── SanitizeDataProcessor.php │ │ ├── SanitizeHttpHeadersProcessor.php │ │ └── SanitizeStacktraceProcessor.php │ │ ├── ReprSerializer.php │ │ ├── SanitizeDataProcessor.php │ │ ├── Serializer.php │ │ ├── Stacktrace.php │ │ ├── TransactionStack.php │ │ ├── Util.php │ │ └── data │ │ └── cacert.pem │ └── phpunit.xml └── modman /README.md: -------------------------------------------------------------------------------- 1 | # Magento To Sentry Logger 2 | 3 | The purpose of this project is to log the magento error and exception messages to sentry, too. This extension is an extension of the [Firegento Logger module](https://github.com/firegento/firegento-logger), so you need the Logger module to use the Sentry logger. 4 | 5 | # Installation 6 | ## Installing Without Composer 7 | If you're not using composer to manage non-magento 3rd party packages, then you can install simply by: 8 | 1. Pull down the code somewhere (`git clone git@github.com:magento-hackathon/LoggerSentry.git`) 9 | 2. Copy over all files associatively. 10 | 3. Configure the module (see below). 11 | 12 | ## Installtion With Composer 13 | Add to your repositories: 14 | 15 | ``` 16 | "repositories": [ 17 | { 18 | "type": "composer", 19 | "url": "http://packages.firegento.com" 20 | } 21 | ], 22 | ``` 23 | 24 | Install with composer: 25 | 26 | `composer require magento-hackathon/loggersentry` 27 | 28 | Additional requirements: 29 | 30 | [firegento/logger](https://github.com/firegento/firegento-logger) 31 | 32 | ## Configuration 33 | 34 | After you install the module you can configure it in the backend at: `System > Configuration > Advanced > FireGento Logger > Sentry Logger` 35 | 36 | ## Further Information 37 | 38 | ### Core Contributors 39 | 40 | * Fabian Blechschmidt 41 | 42 | ### Current Status of Project 43 | 44 | Complete, but needs to be tested in the wild. If there are problems, just open an issue, we'll have a look on it. 45 | -------------------------------------------------------------------------------- /app/code/community/Hackathon/LoggerSentry/Helper/Data.php: -------------------------------------------------------------------------------- 1 | 'fatal', 22 | 1 => 'fatal', 23 | 2 => 'fatal', 24 | 3 => 'error', 25 | 4 => 'warning', 26 | 5 => 'info', 27 | 6 => 'info', 28 | 7 => 'debug' 29 | ); 30 | 31 | /** 32 | * 33 | * 34 | * ignore filename - it is Zend_Log_Writer_Abstract dependency 35 | * 36 | * @param string $filename 37 | * 38 | * @return \Hackathon_LoggerSentry_Model_Sentry 39 | */ 40 | public function __construct($filename) 41 | { 42 | /* @var $helper Hackathon_Logger_Helper_Data */ 43 | $helper = Mage::helper('firegento_logger'); 44 | $options = array( 45 | 'logger' => $helper->getLoggerConfig('sentry/logger_name') 46 | ); 47 | try { 48 | $this->_sentryClient = new Raven_Client($helper->getLoggerConfig('sentry/apikey'), $options); 49 | } catch (Exception $e) { 50 | // Ignore errors so that it doesn't crush the website when/if Sentry goes down. 51 | } 52 | 53 | } 54 | 55 | /** 56 | * Places event line into array of lines to be used as message body. 57 | * 58 | * @param FireGento_Logger_Model_Event $event Event data 59 | * 60 | * @throws Zend_Log_Exception 61 | * @return void 62 | */ 63 | protected function _write($eventObj) 64 | { 65 | try { 66 | /* @var $helper Hackathon_Logger_Helper_Data */ 67 | $helper = Mage::helper('firegento_logger'); 68 | $helper->addEventMetadata($eventObj); 69 | 70 | $event = $eventObj->getEventDataArray(); 71 | 72 | $additional = array( 73 | 'file' => $event['file'], 74 | 'line' => $event['line'], 75 | ); 76 | 77 | foreach (array('REQUEST_METHOD', 'REQUEST_URI', 'REMOTE_IP', 'HTTP_USER_AGENT') as $key) { 78 | if (!empty($event[$key])) { 79 | $additional[$key] = $event[$key]; 80 | } 81 | } 82 | 83 | $this->_assumePriorityByMessage($event); 84 | 85 | // if we still can't figure it out, assume it's an error 86 | $priority = isset($event['priority']) && !empty($event['priority']) ? $event['priority'] : 3; 87 | 88 | if (!$this->_isHighEnoughPriorityToReport($priority)) { 89 | return $this; // Don't log anything warning or less severe. 90 | } 91 | 92 | $this->_sentryClient->captureMessage( 93 | $event['message'], array(), $this->_priorityToLevelMapping[$priority], true, $additional 94 | ); 95 | 96 | } catch (Exception $e) { 97 | throw new Zend_Log_Exception($e->getMessage(), $e->getCode()); 98 | } 99 | } 100 | 101 | /** 102 | * @param int $priority 103 | * @return boolean True if we should be reporting this, false otherwise. 104 | */ 105 | protected function _isHighEnoughPriorityToReport($priority) 106 | { 107 | if ($priority > (int)Mage::helper('firegento_logger')->getLoggerConfig('sentry/priority')) { 108 | return false; // Don't log anything warning or less severe than configured. 109 | } 110 | return true; 111 | } 112 | 113 | /** 114 | * Try to attach a priority # based on the error message string (since sometimes it is not specified) 115 | * @param FireGento_Logger_Model_Event &$event Event data 116 | * @return \Hackathon_LoggerSentry_Model_Sentry 117 | */ 118 | protected function _assumePriorityByMessage(&$event) 119 | { 120 | if (stripos($event['message'], "warn") === 0) { 121 | $event['priority'] = 4; 122 | } 123 | if (stripos($event['message'], "notice") === 0) { 124 | $event['priority'] = 5; 125 | } 126 | 127 | return $this; 128 | } 129 | 130 | /** 131 | * Satisfy newer Zend Framework 132 | * 133 | * @static 134 | * 135 | * @param $config 136 | * 137 | * @return void 138 | */ 139 | static public function factory($config) 140 | { 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /app/code/community/Hackathon/LoggerSentry/etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 1.1.0 6 | 7 | 8 | 9 | 10 | 11 | Hackathon_LoggerSentry_Model 12 | 13 | 14 | 15 | 16 | Hackathon_LoggerSentry_Helper 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | Hackathon_LoggerSentry_Model_Sentry 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | https://XXXXXXXXXXXXXXX:XXXXXXXXXXXXX@app.getsentry.com/7202 34 | Magento Sentry Logger 35 | 4 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/code/community/Hackathon/LoggerSentry/etc/system.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | text 9 | 90 10 | 1 11 | 12 | 13 | 14 | textarea 15 | 0 16 | 1 17 | Add here your API key 18 | 19 | 20 | 21 | text 22 | 10 23 | 1 24 | 25 | 26 | 27 | select 28 | firegento_logger/system_config_source_prioritydefault 29 | 20 30 | 1 31 | Choose the lowest priority level to be sent to Sentry. If a log level is lower priority than this then it will not be sent to Sentry. 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/etc/modules/Hackathon_LoggerSentry.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | community 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "magento-hackathon/loggersentry", 3 | "license": [ 4 | "GPL-3.0" 5 | ], 6 | "type": "magento-module", 7 | "description": "Extension for Firegento Logger module to log to Sentry Service, too.", 8 | "repositories": [ 9 | { 10 | "type": "vcs", 11 | "url": "https://github.com/magento-hackathon/magento-composer-installer" 12 | }, 13 | { 14 | "type": "composer", 15 | "url": "https://packages.firegento.com" 16 | } 17 | ], 18 | "require": { 19 | "magento-hackathon/magento-composer-installer": "*", 20 | "firegento/logger": "1.*" 21 | }, 22 | "extra": { 23 | "magento-root-dir": "./", 24 | "magento-deploystrategy": "link", 25 | "map": [ 26 | [ 27 | "./app/code/community/Hackathon/LoggerSentry", 28 | "./app/code/community/Hackathon/LoggerSentry" 29 | ], 30 | [ 31 | "./lib/sentry", 32 | "./lib/sentry" 33 | ], 34 | [ 35 | "./app/etc/modules/Hackathon_LoggerSentry.xml", 36 | "./app/etc/modules/Hackathon_LoggerSentry.xml" 37 | ] 38 | ] 39 | }, 40 | "authors": [ 41 | { 42 | "name": "Schrank", 43 | "email": "", 44 | "homepage": "https://github.com/Schrank", 45 | "role": "Developer" 46 | }, 47 | { 48 | "name": "Jay El-Kaake", 49 | "email": "", 50 | "homepage": "https://github.com/jayelkaake", 51 | "role": "Developer" 52 | }, 53 | { 54 | "name": "Sylvain Rayé", 55 | "email": "", 56 | "homepage": "https://github.com/diglin", 57 | "role": "Developer" 58 | }, 59 | { 60 | "name": "Kevin Krieger", 61 | "email": "kk@kkrieger.de", 62 | "homepage": "https://kkrieger.de", 63 | "role": "Developer" 64 | } 65 | ] 66 | } -------------------------------------------------------------------------------- /lib/sentry/sentry/.gitattributes: -------------------------------------------------------------------------------- 1 | /examples export-ignore 2 | /docs export-ignore 3 | /test export-ignore 4 | -------------------------------------------------------------------------------- /lib/sentry/sentry/.gitignore: -------------------------------------------------------------------------------- 1 | *.lock 2 | package.xml 3 | /vendor 4 | .idea 5 | .php_cs.cache 6 | docs/_build 7 | test/clover.xml 8 | -------------------------------------------------------------------------------- /lib/sentry/sentry/.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs/_sentryext"] 2 | path = docs/_sentryext 3 | url = https://github.com/getsentry/sentry-doc-support 4 | -------------------------------------------------------------------------------- /lib/sentry/sentry/.php_cs: -------------------------------------------------------------------------------- 1 | in(__DIR__) 5 | ; 6 | 7 | return Symfony\CS\Config\Config::create() 8 | ->setUsingCache(true) 9 | ->setUsingLinter(true) 10 | ->level(Symfony\CS\FixerInterface::PSR2_LEVEL) 11 | ->finder($finder) 12 | ; 13 | -------------------------------------------------------------------------------- /lib/sentry/sentry/.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | tools: 2 | php_sim: false 3 | php_pdepend: true 4 | php_analyzer: true 5 | php_code_coverage: true 6 | external_code_coverage: 7 | timeout: 2400 # There can be another pull request in progress 8 | runs: 6 # PHP 5.3 + PHP 5.4 + PHP 5.5 + PHP 5.6 + PHP 7.0 + PHP 7.1 9 | 10 | build: 11 | environment: 12 | php: 13 | version: 5.6.0 14 | redis: false 15 | postgresql: false 16 | mongodb: false 17 | 18 | filter: 19 | excluded_paths: [vendor/*, test/*, bin/*, docs/*, examples/*] 20 | -------------------------------------------------------------------------------- /lib/sentry/sentry/.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | sudo: false 3 | 4 | php: 5 | - 5.3 6 | - 5.4 7 | - 5.5 8 | - 5.6 9 | - 7.0 10 | - 7.1 11 | - nightly 12 | env: 13 | - REMOVE_XDEBUG="0" 14 | - REMOVE_XDEBUG="1" 15 | 16 | matrix: 17 | allow_failures: 18 | - php: hhvm-3.12 19 | - php: nightly 20 | fast_finish: true 21 | include: 22 | - php: hhvm-3.12 23 | env: REMOVE_XDEBUG="0" HHVM="1" 24 | dist: trusty 25 | 26 | cache: 27 | directories: 28 | - $HOME/.composer/cache 29 | 30 | before_install: 31 | - if [ "$REMOVE_XDEBUG" = "1" ]; then phpenv config-rm xdebug.ini; fi 32 | - composer self-update 33 | 34 | install: travis_retry composer install --no-interaction --prefer-dist 35 | 36 | script: 37 | - composer phpcs 38 | - composer tests-travis 39 | 40 | after_script: 41 | - wget https://scrutinizer-ci.com/ocular.phar 42 | - 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 43 | - 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 44 | - 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 45 | - 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 46 | - 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 47 | - 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 48 | -------------------------------------------------------------------------------- /lib/sentry/sentry/AUTHORS: -------------------------------------------------------------------------------- 1 | The Sentry PHP SDK was originally written by Michael van Tellingen 2 | and is maintained by the Sentry Team. 3 | 4 | http://github.com/getsentry/sentry-php/contributors 5 | -------------------------------------------------------------------------------- /lib/sentry/sentry/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## Unreleased 4 | 5 | ## 1.7.1 (2017-08-02) 6 | - Fix of filtering sensitive data when there is an exception with multiple 'values' (#483) 7 | 8 | ## 1.7.0 (2017-06-07) 9 | 10 | - Corrected some issues with argument serialization in stacktraces (#399). 11 | - The default exception handler will now re-raise exceptions when `call_existing` is true and no exception handler is registered (#421). 12 | - Collect `User.ip_address` automatically (#419). 13 | - Added a processor to remove web cookies. It will be enabled by default in `2.0` (#405). 14 | - 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). 15 | - Added a processor to sanitize HTTP headers (e.g. the Authorization header) (#428). 16 | - Added a processor to remove `pre_context`, `context_line` and `post_context` informations from reported exceptions (#429). 17 | 18 | ## 1.6.2 (2017-02-03) 19 | 20 | - Fixed behavior where fatal errors weren't correctly being reported in most situations. 21 | 22 | ## 1.6.1 (2016-12-14) 23 | 24 | - Correct handling of null in `user_context`. 25 | 26 | ## 1.6.0 (2016-12-09) 27 | 28 | - Improved serialization of certain types to be more restrictive. 29 | - `error_types` can now be configured via `RavenClient`. 30 | - Class serialization has been expanded to include attributes. 31 | - The session extension is no longer required. 32 | - Monolog is no longer a required dependency. 33 | - `user_context` now merges by default. 34 | 35 | ## 1.5.0 (2016-09-29) 36 | 37 | - Added named transaction support. 38 | 39 | ## 1.4.0 (2016-09-20) 40 | 41 | This version primarily overhauls the exception/stacktrace generation to fix 42 | a few bugs and improve the quality of data (#359). 43 | 44 | - Added `excluded_app_paths` config. 45 | - Removed `shift_vars` config. 46 | - Correct fatal error handling to only operate on expected types. This also fixes some behavior with the error suppression operator. 47 | - Expose anonymous and similar frames in the stacktrace. 48 | - Default `prefixes` to PHP's include paths. 49 | - Remove `module` usage. 50 | - Better handle empty argument context. 51 | - Correct alignment of filename (current frame) and function (caller frame) 52 | 53 | ## 1.3.0 (2016-12-19) 54 | 55 | - Fixed an issue causing the error suppression operator to not be respected (#335) 56 | - Fixed some serialization behavior (#352) 57 | - Fixed an issue with app paths and trailing slashes (#350) 58 | - Handle non-latin encoding with source code context line (#345) 59 | 60 | ## 1.2.0 (2016-12-08) 61 | 62 | - Handle non-latin encoding in source code and exception values (#342) 63 | - Ensure pending events are sent on shutdown by default (#338) 64 | - Add `captureLastError` helper (#334) 65 | - Dont report duplicate errors with fatal error handler (#334) 66 | - Enforce maximum length for string serialization (#329) 67 | 68 | ## 1.1.0 (2016-07-30) 69 | 70 | - Uncoercable values should no longer prevent exceptions from sending 71 | to the Sentry server. 72 | - `install()` can no longer be called multiple times. 73 | 74 | ## 1.0.0 (2016-07-28) 75 | 76 | - Removed deprecated error codes configuration from ErrorHandler. 77 | - Removed env data from HTTP interface. 78 | - Removed `message` attribute from exceptions. 79 | - appPath and prefixes are now resolved fully. 80 | - Fixed various getter methods requiring invalid args. 81 | - Fixed data mutation with `send_callback`. 82 | 83 | ## 0.22.0 (2016-06-23) 84 | 85 | - Improve handling of encodings. 86 | - Improve resiliency of variable serialization. 87 | - Add 'formatted' attribute to Message interface. 88 | 89 | ## 0.21.0 (2016-06-10) 90 | 91 | - Added `transport` option. 92 | - Added `install()` shortcut. 93 | 94 | ## 0.20.0 (2016-06-02) 95 | 96 | - Handle missing function names on frames. 97 | - Remove suppression operator usage in breadcrumbs buffer. 98 | - Force serialization of context values. 99 | 100 | ## 0.19.0 (2016-05-27) 101 | 102 | - Add `error_reporting` breadcrumb handler. 103 | 104 | ## 0.18.0 (2016-05-17) 105 | 106 | - Remove session from serialized data. 107 | - `send_callback` return value must now be false to prevent capture. 108 | - Add various getter/setter methods for configuration. 109 | 110 | ## 0.17.0 (2016-05-11) 111 | 112 | - Don't attempt to serialize fixed SDK inputs. 113 | - Improvements to breadcrumbs support in Monolog. 114 | 115 | ## 0.16.0 (2016-05-03) 116 | 117 | - Initial breadcrumbs support with Monolog handler. 118 | 119 | ## 0.15.0 (2016-04-29) 120 | 121 | - Fixed some cases where serialization wouldn't happen. 122 | - Added sdk attribute. 123 | 124 | ## 0.14.0 (2016-04-27) 125 | 126 | - Added `prefixes` option for stripping absolute paths. 127 | - Removed `abs_path` from stacktraces. 128 | - Added `app_path` to specify application root for resolving `in_app` on frames. 129 | - Moved Laravel support to `sentry-laravel` project. 130 | - Fixed duplicate stack computation. 131 | - Added `dsn` option to ease configuration. 132 | - Fixed an issue with the curl async transport. 133 | - Improved serialization of values. 134 | 135 | ## 0.13.0 (2015-09-09) 136 | 137 | - Updated API to use new style interfaces. 138 | - Remove session cookie in default processor. 139 | - Expand docs for Laravel, Symfony2, and Monolog. 140 | - Default error types can now be set as part of ErrorHandler configuration. 141 | 142 | ## 0.12.1 (2015-07-26) 143 | 144 | - Dont send empty values for various context. 145 | 146 | ## 0.12.0 (2015-05-19) 147 | 148 | - Bumped protocol version to 6. 149 | - Fixed an issue with the async curl handler (GH-216). 150 | - Removed UDP transport. 151 | 152 | ## 0.11.0 (2015-03-25) 153 | 154 | - New configuration parameter: `release` 155 | - New configuration parameter: `message_limit` 156 | - New configuration parameter: `curl_ssl_version` 157 | - New configuration parameter: `curl_ipv4` 158 | - New configuration parameter: `verify_ssl` 159 | - Updated remote endpoint to use modern project-based path. 160 | - Expanded default sanitizer support to include `auth_pw` attribute. 161 | 162 | ## 0.10.0 (2014-09-03) 163 | 164 | - Added a default certificate bundle which includes common root CA's as well as getsentry.com's CA. 165 | 166 | ## 0.9.1 (2014-08-26) 167 | 168 | - Change default curl connection to `sync` 169 | - Improve CLI reporting 170 | 171 | ## 0.9.0 (2014-06-04) 172 | 173 | - Protocol version 5 174 | - Default to asynchronous HTTP handler using curl_multi. 175 | 176 | 177 | (For previous versions see the commit history) 178 | -------------------------------------------------------------------------------- /lib/sentry/sentry/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Sentry Team and individual contributors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 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. 9 | 10 | 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. 11 | 12 | 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. 13 | -------------------------------------------------------------------------------- /lib/sentry/sentry/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test 2 | 3 | develop: update-submodules 4 | composer install --dev 5 | make setup-git 6 | 7 | update-submodules: 8 | git submodule init 9 | git submodule update 10 | 11 | cs: 12 | vendor/bin/php-cs-fixer fix --config-file=.php_cs --verbose --diff 13 | 14 | cs-dry-run: 15 | vendor/bin/php-cs-fixer fix --config-file=.php_cs --verbose --diff --dry-run 16 | 17 | test: cs-dry-run 18 | vendor/bin/phpunit 19 | 20 | setup-git: 21 | git config branch.autosetuprebase always 22 | -------------------------------------------------------------------------------- /lib/sentry/sentry/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 | # Sentry for PHP 8 | 9 | [![Build Status](https://secure.travis-ci.org/getsentry/sentry-php.png?branch=master)](http://travis-ci.org/getsentry/sentry-php) 10 | [![Total Downloads](https://img.shields.io/packagist/dt/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) 11 | [![Downloads per month](https://img.shields.io/packagist/dm/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) 12 | [![Latest stable version](https://img.shields.io/packagist/v/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) 13 | [![License](http://img.shields.io/packagist/l/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) 14 | [![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/getsentry/sentry-php/master.svg)](https://scrutinizer-ci.com/g/getsentry/sentry-php/) 15 | [![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/getsentry/sentry-php/master.svg)](https://scrutinizer-ci.com/g/getsentry/sentry-php/) 16 | 17 | The Sentry PHP error reporter tracks errors and exceptions that happen during the 18 | execution of your application and provides instant notification with detailed 19 | informations needed to prioritize, identify, reproduce and fix each issue. Learn 20 | more about [automatic PHP error reporting with Sentry](https://sentry.io/for/php/). 21 | 22 | ## Features 23 | 24 | - Automatically report (un)handled exceptions and errors 25 | - Send customized diagnostic data 26 | - Process and sanitize data before sending it over the network 27 | 28 | ## Usage 29 | 30 | ```php 31 | // Instantiate a new client with a compatible DSN and install built-in 32 | // handlers 33 | $client = (new Raven_Client('http://public:secret@example.com/1'))->install(); 34 | 35 | // Capture an exception 36 | $event_id = $client->captureException($ex); 37 | 38 | // Give the user feedback 39 | echo "Sorry, there was an error!"; 40 | echo "Your reference ID is " . $event_id; 41 | ``` 42 | 43 | For more information, see our [documentation](https://docs.getsentry.com/hosted/clients/php/). 44 | 45 | 46 | ## Integration with frameworks 47 | 48 | Other packages exists to integrate this SDK into the most common frameworks. 49 | 50 | ### Official integrations 51 | 52 | The following integrations are fully supported and maintained by the Sentry team. 53 | 54 | - [Symfony](https://github.com/getsentry/sentry-symfony) 55 | - [Laravel](https://github.com/getsentry/sentry-laravel) 56 | 57 | ### 3rd party integrations 58 | 59 | The following integrations are available and maintained by members of the Sentry community. 60 | 61 | - [Nette](https://github.com/Salamek/raven-nette) 62 | - [ZendFramework](https://github.com/facile-it/sentry-module) 63 | - [WordPress](https://wordpress.org/plugins/wp-sentry-integration/) 64 | - [Drupal](https://www.drupal.org/project/raven) 65 | - [OpenCart](https://github.com/BurdaPraha/oc_sentry) 66 | - ... feel free to be famous, create a port to your favourite platform! 67 | 68 | ## Community 69 | 70 | - [Documentation](https://docs.getsentry.com/hosted/clients/php/) 71 | - [Bug Tracker](http://github.com/getsentry/sentry-php/issues) 72 | - [Code](http://github.com/getsentry/sentry-php) 73 | - [Mailing List](https://groups.google.com/group/getsentry) 74 | - [IRC](irc://irc.freenode.net/sentry) (irc.freenode.net, #sentry) 75 | 76 | 77 | Contributing 78 | ------------ 79 | 80 | Dependencies are managed through composer: 81 | 82 | ``` 83 | $ composer install 84 | ``` 85 | 86 | Tests can then be run via phpunit: 87 | 88 | ``` 89 | $ vendor/bin/phpunit 90 | ``` 91 | 92 | 93 | Tagging a Release 94 | ----------------- 95 | 96 | 1. Make sure ``CHANGES`` is up to date (add the release date) and ``master`` is green. 97 | 98 | 2. Create a new branch for the minor version (if not present): 99 | 100 | ``` 101 | $ git checkout -b releases/1.7.x 102 | ``` 103 | 104 | 3. Update the hardcoded version tag in ``Client.php``: 105 | 106 | ``` 107 | class Raven_Client 108 | { 109 | const VERSION = '1.7.0'; 110 | } 111 | ``` 112 | 113 | 4. Commit the change: 114 | 115 | ``` 116 | $ git commit -a -m "1.7.0" 117 | ``` 118 | 119 | 5. Tag the branch: 120 | 121 | ``` 122 | git tag 1.7.0 123 | ``` 124 | 125 | 6. Push the tag: 126 | 127 | ``` 128 | git push --tags 129 | ``` 130 | 131 | 7. Switch back to ``master``: 132 | 133 | ``` 134 | git checkout master 135 | ``` 136 | 137 | 8. Add the next minor release to the ``CHANGES`` file: 138 | 139 | ``` 140 | ## 1.8.0 (unreleased) 141 | ``` 142 | 143 | 9. Update the version in ``Client.php``: 144 | 145 | ``` 146 | class Raven_Client 147 | { 148 | const VERSION = '1.8.x-dev'; 149 | } 150 | ``` 151 | 152 | 10. Lastly, update the composer version in ``composer.json``: 153 | 154 | ``` 155 | "extra": { 156 | "branch-alias": { 157 | "dev-master": "1.8.x-dev" 158 | } 159 | } 160 | ``` 161 | 162 | All done! Composer will pick up the tag and configuration automatically. 163 | -------------------------------------------------------------------------------- /lib/sentry/sentry/bin/sentry: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getMessage()); 33 | } 34 | 35 | $client = new Raven_Client($dsn, array( 36 | 'trace' => true, 37 | 'curl_method' => 'sync', 38 | 'app_path' => realpath(__DIR__ . '/..'), 39 | 'base_path' => realpath(__DIR__ . '/..'), 40 | )); 41 | 42 | $config = get_object_vars($client); 43 | $required_keys = array('server', 'project', 'public_key', 'secret_key'); 44 | 45 | echo "Client configuration:\n"; 46 | foreach ($required_keys as $key) { 47 | if (empty($config[$key])) { 48 | exit("ERROR: Missing configuration for $key"); 49 | } 50 | if (is_array($config[$key])) { 51 | echo "-> $key: [".implode(", ", $config[$key])."]\n"; 52 | } else { 53 | echo "-> $key: $config[$key]\n"; 54 | } 55 | 56 | } 57 | echo "\n"; 58 | 59 | echo "Sending a test event:\n"; 60 | 61 | $ex = raven_cli_test("command name", array("foo" => "bar")); 62 | $event_id = $client->captureException($ex); 63 | 64 | echo "-> event ID: $event_id\n"; 65 | 66 | $last_error = $client->getLastError(); 67 | if (!empty($last_error)) { 68 | exit("ERROR: There was an error sending the test event:\n " . $last_error); 69 | } 70 | 71 | echo "\n"; 72 | echo "Done!"; 73 | } 74 | 75 | 76 | function main() { 77 | global $argv; 78 | 79 | if (!isset($argv[1])) { 80 | exit('Usage: sentry test '); 81 | } 82 | 83 | $cmd = $argv[1]; 84 | 85 | switch ($cmd) { 86 | case 'test': 87 | cmd_test(@$argv[2]); 88 | break; 89 | default: 90 | exit('Usage: sentry test '); 91 | } 92 | } 93 | 94 | main(); 95 | -------------------------------------------------------------------------------- /lib/sentry/sentry/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sentry/sentry", 3 | "type": "library", 4 | "description": "A PHP client for Sentry (http://getsentry.com)", 5 | "keywords": ["log", "logging"], 6 | "homepage": "http://getsentry.com", 7 | "license": "BSD-3-Clause", 8 | "authors": [ 9 | { 10 | "name": "David Cramer", 11 | "email": "dcramer@gmail.com" 12 | } 13 | ], 14 | "require-dev": { 15 | "friendsofphp/php-cs-fixer": "^1.8.0", 16 | "phpunit/phpunit": "^4.8 || ^5.0", 17 | "monolog/monolog": "*" 18 | }, 19 | "require": { 20 | "php": "^5.3|^7.0", 21 | "ext-curl": "*" 22 | }, 23 | "suggest": { 24 | "ext-hash": "*", 25 | "ext-json": "*", 26 | "ext-mbstring": "*", 27 | "immobiliare/sentry-php": "Fork that fixes support for PHP 5.2", 28 | "monolog/monolog": "Automatically capture Monolog events as breadcrumbs" 29 | }, 30 | "conflict": { 31 | "raven/raven": "*" 32 | }, 33 | "bin": [ 34 | "bin/sentry" 35 | ], 36 | "autoload": { 37 | "psr-0" : { 38 | "Raven_" : "lib/" 39 | } 40 | }, 41 | "scripts": { 42 | "tests": [ 43 | "vendor/bin/phpunit --verbose" 44 | ], 45 | "tests-travis": [ 46 | "vendor/bin/phpunit --verbose --configuration phpunit.xml --coverage-clover test/clover.xml" 47 | ], 48 | "tests-report": [ 49 | "vendor/bin/phpunit --verbose --configuration phpunit.xml --coverage-html test/html-report" 50 | ], 51 | "phpcs": [ 52 | "vendor/bin/php-cs-fixer fix --config-file=.php_cs --verbose --diff --dry-run" 53 | ] 54 | }, 55 | "extra": { 56 | "branch-alias": { 57 | "dev-master": "1.8.x-dev" 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/sentry/sentry/lib/Raven/Autoloader.php: -------------------------------------------------------------------------------- 1 | size = $size; 30 | $this->reset(); 31 | } 32 | 33 | public function reset() 34 | { 35 | $this->count = 0; 36 | $this->pos = 0; 37 | $this->buffer = array(); 38 | } 39 | 40 | public function record($crumb) 41 | { 42 | if (empty($crumb['timestamp'])) { 43 | $crumb['timestamp'] = microtime(true); 44 | } 45 | $this->buffer[$this->pos] = $crumb; 46 | $this->pos = ($this->pos + 1) % $this->size; 47 | $this->count++; 48 | } 49 | 50 | /** 51 | * @return array[] 52 | */ 53 | public function fetch() 54 | { 55 | $results = array(); 56 | for ($i = 0; $i <= ($this->size - 1); $i++) { 57 | $idx = ($this->pos + $i) % $this->size; 58 | if (isset($this->buffer[$idx])) { 59 | $results[] = $this->buffer[$idx]; 60 | } 61 | } 62 | return $results; 63 | } 64 | 65 | public function is_empty() 66 | { 67 | return $this->count === 0; 68 | } 69 | 70 | public function to_json() 71 | { 72 | return array( 73 | 'values' => $this->fetch(), 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/sentry/sentry/lib/Raven/Breadcrumbs/ErrorHandler.php: -------------------------------------------------------------------------------- 1 | ravenClient = $ravenClient; 18 | } 19 | 20 | public function handleError($code, $message, $file = '', $line = 0, $context = array()) 21 | { 22 | $this->ravenClient->breadcrumbs->record(array( 23 | 'category' => 'error_reporting', 24 | 'message' => $message, 25 | 'level' => $this->ravenClient->translateSeverity($code), 26 | 'data' => array( 27 | 'code' => $code, 28 | 'line' => $line, 29 | 'file' => $file, 30 | ), 31 | )); 32 | 33 | if ($this->existingHandler !== null) { 34 | return call_user_func($this->existingHandler, $code, $message, $file, $line, $context); 35 | } else { 36 | return false; 37 | } 38 | } 39 | 40 | public function install() 41 | { 42 | $this->existingHandler = set_error_handler(array($this, 'handleError'), E_ALL); 43 | return $this; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/sentry/sentry/lib/Raven/Breadcrumbs/MonologHandler.php: -------------------------------------------------------------------------------- 1 | Raven_Client::DEBUG, 13 | Logger::INFO => Raven_Client::INFO, 14 | Logger::NOTICE => Raven_Client::INFO, 15 | Logger::WARNING => Raven_Client::WARNING, 16 | Logger::ERROR => Raven_Client::ERROR, 17 | Logger::CRITICAL => Raven_Client::FATAL, 18 | Logger::ALERT => Raven_Client::FATAL, 19 | Logger::EMERGENCY => Raven_Client::FATAL, 20 | ); 21 | 22 | protected $excMatch = '/^exception \'([^\']+)\' with message \'(.+)\' in .+$/s'; 23 | 24 | /** 25 | * @var Raven_Client the client object that sends the message to the server 26 | */ 27 | protected $ravenClient; 28 | 29 | /** 30 | * @param Raven_Client $ravenClient 31 | * @param int $level The minimum logging level at which this handler will be triggered 32 | * @param bool $bubble Whether the messages that are handled can bubble up the stack or not 33 | */ 34 | public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true) 35 | { 36 | parent::__construct($level, $bubble); 37 | 38 | $this->ravenClient = $ravenClient; 39 | } 40 | 41 | /** 42 | * @param string $message 43 | * @return array|null 44 | */ 45 | protected function parseException($message) 46 | { 47 | if (preg_match($this->excMatch, $message, $matches)) { 48 | return array($matches[1], $matches[2]); 49 | } 50 | 51 | return null; 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | protected function write(array $record) 58 | { 59 | // sentry uses the 'nobreadcrumb' attribute to skip reporting 60 | if (!empty($record['context']['nobreadcrumb'])) { 61 | return; 62 | } 63 | 64 | if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) { 65 | /** 66 | * @var Exception $exc 67 | */ 68 | $exc = $record['context']['exception']; 69 | $crumb = array( 70 | 'type' => 'error', 71 | 'level' => $this->logLevels[$record['level']], 72 | 'category' => $record['channel'], 73 | 'data' => array( 74 | 'type' => get_class($exc), 75 | 'value' => $exc->getMessage(), 76 | ), 77 | ); 78 | } else { 79 | // TODO(dcramer): parse exceptions out of messages and format as above 80 | if ($error = $this->parseException($record['message'])) { 81 | $crumb = array( 82 | 'type' => 'error', 83 | 'level' => $this->logLevels[$record['level']], 84 | 'category' => $record['channel'], 85 | 'data' => array( 86 | 'type' => $error[0], 87 | 'value' => $error[1], 88 | ), 89 | ); 90 | } else { 91 | $crumb = array( 92 | 'level' => $this->logLevels[$record['level']], 93 | 'category' => $record['channel'], 94 | 'message' => $record['message'], 95 | ); 96 | } 97 | } 98 | 99 | $this->ravenClient->breadcrumbs->record($crumb); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/sentry/sentry/lib/Raven/Client.php: -------------------------------------------------------------------------------- 1 | logger = Raven_Util::get($options, 'logger', 'php'); 146 | $this->server = Raven_Util::get($options, 'server'); 147 | $this->secret_key = Raven_Util::get($options, 'secret_key'); 148 | $this->public_key = Raven_Util::get($options, 'public_key'); 149 | $this->project = Raven_Util::get($options, 'project', 1); 150 | $this->auto_log_stacks = (bool) Raven_Util::get($options, 'auto_log_stacks', false); 151 | $this->name = Raven_Util::get($options, 'name', Raven_Compat::gethostname()); 152 | $this->site = Raven_Util::get($options, 'site', self::_server_variable('SERVER_NAME')); 153 | $this->tags = Raven_Util::get($options, 'tags', array()); 154 | $this->release = Raven_Util::get($options, 'release', null); 155 | $this->environment = Raven_Util::get($options, 'environment', null); 156 | $this->sample_rate = Raven_Util::get($options, 'sample_rate', 1); 157 | $this->trace = (bool) Raven_Util::get($options, 'trace', true); 158 | $this->timeout = Raven_Util::get($options, 'timeout', 2); 159 | $this->message_limit = Raven_Util::get($options, 'message_limit', self::MESSAGE_LIMIT); 160 | $this->exclude = Raven_Util::get($options, 'exclude', array()); 161 | $this->severity_map = null; 162 | $this->http_proxy = Raven_Util::get($options, 'http_proxy'); 163 | $this->extra_data = Raven_Util::get($options, 'extra', array()); 164 | $this->send_callback = Raven_Util::get($options, 'send_callback', null); 165 | $this->curl_method = Raven_Util::get($options, 'curl_method', 'sync'); 166 | $this->curl_path = Raven_Util::get($options, 'curl_path', 'curl'); 167 | $this->curl_ipv4 = Raven_Util::get($options, 'curl_ipv4', true); 168 | $this->ca_cert = Raven_Util::get($options, 'ca_cert', static::get_default_ca_cert()); 169 | $this->verify_ssl = Raven_Util::get($options, 'verify_ssl', true); 170 | $this->curl_ssl_version = Raven_Util::get($options, 'curl_ssl_version'); 171 | $this->trust_x_forwarded_proto = Raven_Util::get($options, 'trust_x_forwarded_proto'); 172 | $this->transport = Raven_Util::get($options, 'transport', null); 173 | $this->mb_detect_order = Raven_Util::get($options, 'mb_detect_order', null); 174 | $this->error_types = Raven_Util::get($options, 'error_types', null); 175 | 176 | // app path is used to determine if code is part of your application 177 | $this->setAppPath(Raven_Util::get($options, 'app_path', null)); 178 | $this->setExcludedAppPaths(Raven_Util::get($options, 'excluded_app_paths', null)); 179 | // a list of prefixes used to coerce absolute paths into relative 180 | $this->setPrefixes(Raven_Util::get($options, 'prefixes', static::getDefaultPrefixes())); 181 | $this->processors = $this->setProcessorsFromOptions($options); 182 | 183 | $this->_lasterror = null; 184 | $this->_last_sentry_error = null; 185 | $this->_curl_instance = null; 186 | $this->_last_event_id = null; 187 | $this->_user = null; 188 | $this->_pending_events = array(); 189 | $this->context = new Raven_Context(); 190 | $this->breadcrumbs = new Raven_Breadcrumbs(); 191 | $this->_shutdown_function_has_been_set = false; 192 | 193 | $this->sdk = Raven_Util::get($options, 'sdk', array( 194 | 'name' => 'sentry-php', 195 | 'version' => self::VERSION, 196 | )); 197 | $this->serializer = new Raven_Serializer($this->mb_detect_order); 198 | $this->reprSerializer = new Raven_ReprSerializer($this->mb_detect_order); 199 | 200 | if ($this->curl_method == 'async') { 201 | $this->_curl_handler = new Raven_CurlHandler($this->get_curl_options()); 202 | } 203 | 204 | $this->transaction = new Raven_TransactionStack(); 205 | if (static::is_http_request() && isset($_SERVER['PATH_INFO'])) { 206 | // @codeCoverageIgnoreStart 207 | $this->transaction->push($_SERVER['PATH_INFO']); 208 | // @codeCoverageIgnoreEnd 209 | } 210 | 211 | if (Raven_Util::get($options, 'install_default_breadcrumb_handlers', true)) { 212 | $this->registerDefaultBreadcrumbHandlers(); 213 | } 214 | 215 | if (Raven_Util::get($options, 'install_shutdown_handler', true)) { 216 | $this->registerShutdownFunction(); 217 | } 218 | } 219 | 220 | public function __destruct() 221 | { 222 | // Force close curl resource 223 | $this->close_curl_resource(); 224 | } 225 | 226 | /** 227 | * Destruct all objects contain link to this object 228 | * 229 | * This method can not delete shutdown handler 230 | */ 231 | public function close_all_children_link() 232 | { 233 | $this->processors = array(); 234 | } 235 | 236 | /** 237 | * Installs any available automated hooks (such as error_reporting). 238 | */ 239 | public function install() 240 | { 241 | if ($this->error_handler) { 242 | throw new Raven_Exception(sprintf('%s->install() must only be called once', get_class($this))); 243 | } 244 | $this->error_handler = new Raven_ErrorHandler($this, false, $this->error_types); 245 | $this->error_handler->registerExceptionHandler(); 246 | $this->error_handler->registerErrorHandler(); 247 | $this->error_handler->registerShutdownFunction(); 248 | return $this; 249 | } 250 | 251 | public function getRelease() 252 | { 253 | return $this->release; 254 | } 255 | 256 | public function setRelease($value) 257 | { 258 | $this->release = $value; 259 | return $this; 260 | } 261 | 262 | public function getEnvironment() 263 | { 264 | return $this->environment; 265 | } 266 | 267 | public function setEnvironment($value) 268 | { 269 | $this->environment = $value; 270 | return $this; 271 | } 272 | 273 | private static function getDefaultPrefixes() 274 | { 275 | $value = get_include_path(); 276 | return explode(PATH_SEPARATOR, $value); 277 | } 278 | 279 | private static function _convertPath($value) 280 | { 281 | $path = @realpath($value); 282 | if ($path === false) { 283 | $path = $value; 284 | } 285 | // we need app_path to have a trailing slash otherwise 286 | // base path detection becomes complex if the same 287 | // prefix is matched 288 | if (substr($path, 0, 1) === DIRECTORY_SEPARATOR && substr($path, -1) !== DIRECTORY_SEPARATOR) { 289 | $path = $path.DIRECTORY_SEPARATOR; 290 | } 291 | return $path; 292 | } 293 | 294 | public function getAppPath() 295 | { 296 | return $this->app_path; 297 | } 298 | 299 | public function setAppPath($value) 300 | { 301 | if ($value) { 302 | $this->app_path = static::_convertPath($value); 303 | } else { 304 | $this->app_path = null; 305 | } 306 | return $this; 307 | } 308 | 309 | public function getExcludedAppPaths() 310 | { 311 | return $this->excluded_app_paths; 312 | } 313 | 314 | public function setExcludedAppPaths($value) 315 | { 316 | $this->excluded_app_paths = $value ? array_map(array($this, '_convertPath'), $value) : null; 317 | return $this; 318 | } 319 | 320 | public function getPrefixes() 321 | { 322 | return $this->prefixes; 323 | } 324 | 325 | /** 326 | * @param array $value 327 | * @return Raven_Client 328 | */ 329 | public function setPrefixes($value) 330 | { 331 | $this->prefixes = $value ? array_map(array($this, '_convertPath'), $value) : $value; 332 | return $this; 333 | } 334 | 335 | public function getSendCallback() 336 | { 337 | return $this->send_callback; 338 | } 339 | 340 | public function setSendCallback($value) 341 | { 342 | $this->send_callback = $value; 343 | return $this; 344 | } 345 | 346 | public function getTransport() 347 | { 348 | return $this->transport; 349 | } 350 | 351 | public function getServerEndpoint($value = '') 352 | { 353 | return $this->server; 354 | } 355 | 356 | public static function getUserAgent() 357 | { 358 | return 'sentry-php/' . self::VERSION; 359 | } 360 | 361 | /** 362 | * Set a custom transport to override how Sentry events are sent upstream. 363 | * 364 | * The bound function will be called with ``$client`` and ``$data`` arguments 365 | * and is responsible for encoding the data, authenticating, and sending 366 | * the data to the upstream Sentry server. 367 | * 368 | * @param Callable $value Function to be called 369 | * @return Raven_Client 370 | */ 371 | public function setTransport($value) 372 | { 373 | $this->transport = $value; 374 | return $this; 375 | } 376 | 377 | /** 378 | * @return string[]|Raven_Processor[] 379 | */ 380 | public static function getDefaultProcessors() 381 | { 382 | return array( 383 | 'Raven_Processor_SanitizeDataProcessor', 384 | ); 385 | } 386 | 387 | /** 388 | * Sets the Raven_Processor sub-classes to be used when data is processed before being 389 | * sent to Sentry. 390 | * 391 | * @param $options 392 | * @return Raven_Processor[] 393 | */ 394 | public function setProcessorsFromOptions($options) 395 | { 396 | $processors = array(); 397 | foreach (Raven_util::get($options, 'processors', static::getDefaultProcessors()) as $processor) { 398 | /** 399 | * @var Raven_Processor $new_processor 400 | * @var Raven_Processor|string $processor 401 | */ 402 | $new_processor = new $processor($this); 403 | 404 | if (isset($options['processorOptions']) && is_array($options['processorOptions'])) { 405 | if (isset($options['processorOptions'][$processor]) 406 | && method_exists($processor, 'setProcessorOptions') 407 | ) { 408 | $new_processor->setProcessorOptions($options['processorOptions'][$processor]); 409 | } 410 | } 411 | $processors[] = $new_processor; 412 | } 413 | return $processors; 414 | } 415 | 416 | /** 417 | * Parses a Raven-compatible DSN and returns an array of its values. 418 | * 419 | * @param string $dsn Raven compatible DSN 420 | * @return array parsed DSN 421 | * 422 | * @doc http://raven.readthedocs.org/en/latest/config/#the-sentry-dsn 423 | */ 424 | public static function parseDSN($dsn) 425 | { 426 | $url = parse_url($dsn); 427 | $scheme = (isset($url['scheme']) ? $url['scheme'] : ''); 428 | if (!in_array($scheme, array('http', 'https'))) { 429 | throw new InvalidArgumentException( 430 | 'Unsupported Sentry DSN scheme: '. 431 | (!empty($scheme) ? $scheme : '') 432 | ); 433 | } 434 | $netloc = (isset($url['host']) ? $url['host'] : null); 435 | $netloc .= (isset($url['port']) ? ':'.$url['port'] : null); 436 | $rawpath = (isset($url['path']) ? $url['path'] : null); 437 | if ($rawpath) { 438 | $pos = strrpos($rawpath, '/', 1); 439 | if ($pos !== false) { 440 | $path = substr($rawpath, 0, $pos); 441 | $project = substr($rawpath, $pos + 1); 442 | } else { 443 | $path = ''; 444 | $project = substr($rawpath, 1); 445 | } 446 | } else { 447 | $project = null; 448 | $path = ''; 449 | } 450 | $username = (isset($url['user']) ? $url['user'] : null); 451 | $password = (isset($url['pass']) ? $url['pass'] : null); 452 | if (empty($netloc) || empty($project) || empty($username) || empty($password)) { 453 | throw new InvalidArgumentException('Invalid Sentry DSN: ' . $dsn); 454 | } 455 | 456 | return array( 457 | 'server' => sprintf('%s://%s%s/api/%s/store/', $scheme, $netloc, $path, $project), 458 | 'project' => $project, 459 | 'public_key' => $username, 460 | 'secret_key' => $password, 461 | ); 462 | } 463 | 464 | public function getLastError() 465 | { 466 | return $this->_lasterror; 467 | } 468 | 469 | /** 470 | * Given an identifier, returns a Sentry searchable string. 471 | * 472 | * @param mixed $ident 473 | * @return mixed 474 | * @codeCoverageIgnore 475 | */ 476 | public function getIdent($ident) 477 | { 478 | // XXX: We don't calculate checksums yet, so we only have the ident. 479 | return $ident; 480 | } 481 | 482 | /** 483 | * @param string $message The message (primary description) for the event. 484 | * @param array $params params to use when formatting the message. 485 | * @param string $level Log level group 486 | * @param bool|array $stack 487 | * @param mixed $vars 488 | * @return string|null 489 | * @deprecated 490 | * @codeCoverageIgnore 491 | */ 492 | public function message($message, $params = array(), $level = self::INFO, 493 | $stack = false, $vars = null) 494 | { 495 | return $this->captureMessage($message, $params, $level, $stack, $vars); 496 | } 497 | 498 | /** 499 | * @param Exception $exception 500 | * @return string|null 501 | * @deprecated 502 | * @codeCoverageIgnore 503 | */ 504 | public function exception($exception) 505 | { 506 | return $this->captureException($exception); 507 | } 508 | 509 | /** 510 | * Log a message to sentry 511 | * 512 | * @param string $message The message (primary description) for the event. 513 | * @param array $params params to use when formatting the message. 514 | * @param array $data Additional attributes to pass with this event (see Sentry docs). 515 | * @param bool|array $stack 516 | * @param mixed $vars 517 | * @return string|null 518 | */ 519 | public function captureMessage($message, $params = array(), $data = array(), 520 | $stack = false, $vars = null) 521 | { 522 | // Gracefully handle messages which contain formatting characters, but were not 523 | // intended to be used with formatting. 524 | if (!empty($params)) { 525 | $formatted_message = vsprintf($message, $params); 526 | } else { 527 | $formatted_message = $message; 528 | } 529 | 530 | if ($data === null) { 531 | $data = array(); 532 | // support legacy method of passing in a level name as the third arg 533 | } elseif (!is_array($data)) { 534 | $data = array( 535 | 'level' => $data, 536 | ); 537 | } 538 | 539 | $data['message'] = $formatted_message; 540 | $data['sentry.interfaces.Message'] = array( 541 | 'message' => $message, 542 | 'params' => $params, 543 | 'formatted' => $formatted_message, 544 | ); 545 | 546 | return $this->capture($data, $stack, $vars); 547 | } 548 | 549 | /** 550 | * Log an exception to sentry 551 | * 552 | * @param Exception $exception The Exception object. 553 | * @param array $data Additional attributes to pass with this event (see Sentry docs). 554 | * @param mixed $logger 555 | * @param mixed $vars 556 | * @return string|null 557 | */ 558 | public function captureException($exception, $data = null, $logger = null, $vars = null) 559 | { 560 | $has_chained_exceptions = version_compare(PHP_VERSION, '5.3.0', '>='); 561 | 562 | if (in_array(get_class($exception), $this->exclude)) { 563 | return null; 564 | } 565 | 566 | if ($data === null) { 567 | $data = array(); 568 | } 569 | 570 | $exc = $exception; 571 | do { 572 | $exc_data = array( 573 | 'value' => $this->serializer->serialize($exc->getMessage()), 574 | 'type' => get_class($exc), 575 | ); 576 | 577 | /**'exception' 578 | * Exception::getTrace doesn't store the point at where the exception 579 | * was thrown, so we have to stuff it in ourselves. Ugh. 580 | */ 581 | $trace = $exc->getTrace(); 582 | $frame_where_exception_thrown = array( 583 | 'file' => $exc->getFile(), 584 | 'line' => $exc->getLine(), 585 | ); 586 | 587 | array_unshift($trace, $frame_where_exception_thrown); 588 | 589 | // manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149) 590 | if (!class_exists('Raven_Stacktrace')) { 591 | // @codeCoverageIgnoreStart 592 | spl_autoload_call('Raven_Stacktrace'); 593 | // @codeCoverageIgnoreEnd 594 | } 595 | 596 | $exc_data['stacktrace'] = array( 597 | 'frames' => Raven_Stacktrace::get_stack_info( 598 | $trace, $this->trace, $vars, $this->message_limit, $this->prefixes, 599 | $this->app_path, $this->excluded_app_paths, $this->serializer, $this->reprSerializer 600 | ), 601 | ); 602 | 603 | $exceptions[] = $exc_data; 604 | } while ($has_chained_exceptions && $exc = $exc->getPrevious()); 605 | 606 | $data['exception'] = array( 607 | 'values' => array_reverse($exceptions), 608 | ); 609 | if ($logger !== null) { 610 | $data['logger'] = $logger; 611 | } 612 | 613 | if (empty($data['level'])) { 614 | if (method_exists($exception, 'getSeverity')) { 615 | $data['level'] = $this->translateSeverity($exception->getSeverity()); 616 | } else { 617 | $data['level'] = self::ERROR; 618 | } 619 | } 620 | 621 | return $this->capture($data, $trace, $vars); 622 | } 623 | 624 | 625 | /** 626 | * Capture the most recent error (obtained with ``error_get_last``). 627 | * @return string|null 628 | */ 629 | public function captureLastError() 630 | { 631 | if (null === $error = error_get_last()) { 632 | return null; 633 | } 634 | 635 | $e = new ErrorException( 636 | @$error['message'], 0, @$error['type'], 637 | @$error['file'], @$error['line'] 638 | ); 639 | 640 | return $this->captureException($e); 641 | } 642 | 643 | /** 644 | * Log an query to sentry 645 | * 646 | * @param string|null $query 647 | * @param string $level 648 | * @param string $engine 649 | */ 650 | public function captureQuery($query, $level = self::INFO, $engine = '') 651 | { 652 | $data = array( 653 | 'message' => $query, 654 | 'level' => $level, 655 | 'sentry.interfaces.Query' => array( 656 | 'query' => $query 657 | ) 658 | ); 659 | 660 | if ($engine !== '') { 661 | $data['sentry.interfaces.Query']['engine'] = $engine; 662 | } 663 | return $this->capture($data, false); 664 | } 665 | 666 | /** 667 | * Return the last captured event's ID or null if none available. 668 | */ 669 | public function getLastEventID() 670 | { 671 | return $this->_last_event_id; 672 | } 673 | 674 | protected function registerDefaultBreadcrumbHandlers() 675 | { 676 | $handler = new Raven_Breadcrumbs_ErrorHandler($this); 677 | $handler->install(); 678 | } 679 | 680 | protected function registerShutdownFunction() 681 | { 682 | if (!$this->_shutdown_function_has_been_set) { 683 | $this->_shutdown_function_has_been_set = true; 684 | register_shutdown_function(array($this, 'onShutdown')); 685 | } 686 | } 687 | 688 | /** 689 | * @return bool 690 | * @codeCoverageIgnore 691 | */ 692 | protected static function is_http_request() 693 | { 694 | return isset($_SERVER['REQUEST_METHOD']) && PHP_SAPI !== 'cli'; 695 | } 696 | 697 | protected function get_http_data() 698 | { 699 | $headers = array(); 700 | 701 | foreach ($_SERVER as $key => $value) { 702 | if (0 === strpos($key, 'HTTP_')) { 703 | $header_key = 704 | str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5))))); 705 | $headers[$header_key] = $value; 706 | } elseif (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH')) && $value !== '') { 707 | $header_key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key)))); 708 | $headers[$header_key] = $value; 709 | } 710 | } 711 | 712 | $result = array( 713 | 'method' => self::_server_variable('REQUEST_METHOD'), 714 | 'url' => $this->get_current_url(), 715 | 'query_string' => self::_server_variable('QUERY_STRING'), 716 | ); 717 | 718 | // dont set this as an empty array as PHP will treat it as a numeric array 719 | // instead of a mapping which goes against the defined Sentry spec 720 | if (!empty($_POST)) { 721 | $result['data'] = $_POST; 722 | } 723 | if (!empty($_COOKIE)) { 724 | $result['cookies'] = $_COOKIE; 725 | } 726 | if (!empty($headers)) { 727 | $result['headers'] = $headers; 728 | } 729 | 730 | return array( 731 | 'request' => $result, 732 | ); 733 | } 734 | 735 | protected function get_user_data() 736 | { 737 | $user = $this->context->user; 738 | if ($user === null) { 739 | if (!function_exists('session_id') || !session_id()) { 740 | return array(); 741 | } 742 | $user = array( 743 | 'id' => session_id(), 744 | ); 745 | if (!empty($_SERVER['REMOTE_ADDR'])) { 746 | $user['ip_address'] = $_SERVER['REMOTE_ADDR']; 747 | } 748 | if (!empty($_SESSION)) { 749 | $user['data'] = $_SESSION; 750 | } 751 | } 752 | return array( 753 | 'user' => $user, 754 | ); 755 | } 756 | 757 | protected function get_extra_data() 758 | { 759 | return $this->extra_data; 760 | } 761 | 762 | public function get_default_data() 763 | { 764 | return array( 765 | 'server_name' => $this->name, 766 | 'project' => $this->project, 767 | 'site' => $this->site, 768 | 'logger' => $this->logger, 769 | 'tags' => $this->tags, 770 | 'platform' => 'php', 771 | 'sdk' => $this->sdk, 772 | 'culprit' => $this->transaction->peek(), 773 | ); 774 | } 775 | 776 | public function capture($data, $stack = null, $vars = null) 777 | { 778 | if (!isset($data['timestamp'])) { 779 | $data['timestamp'] = gmdate('Y-m-d\TH:i:s\Z'); 780 | } 781 | if (!isset($data['level'])) { 782 | $data['level'] = self::ERROR; 783 | } 784 | if (!isset($data['tags'])) { 785 | $data['tags'] = array(); 786 | } 787 | if (!isset($data['extra'])) { 788 | $data['extra'] = array(); 789 | } 790 | if (!isset($data['event_id'])) { 791 | $data['event_id'] = static::uuid4(); 792 | } 793 | 794 | if (isset($data['message'])) { 795 | $data['message'] = substr($data['message'], 0, $this->message_limit); 796 | } 797 | 798 | $data = array_merge($this->get_default_data(), $data); 799 | 800 | if (static::is_http_request()) { 801 | $data = array_merge($this->get_http_data(), $data); 802 | } 803 | 804 | $data = array_merge($this->get_user_data(), $data); 805 | 806 | if ($this->release) { 807 | $data['release'] = $this->release; 808 | } 809 | if ($this->environment) { 810 | $data['environment'] = $this->environment; 811 | } 812 | 813 | $data['tags'] = array_merge( 814 | $this->tags, 815 | $this->context->tags, 816 | $data['tags']); 817 | 818 | $data['extra'] = array_merge( 819 | $this->get_extra_data(), 820 | $this->context->extra, 821 | $data['extra']); 822 | 823 | if (empty($data['extra'])) { 824 | unset($data['extra']); 825 | } 826 | if (empty($data['tags'])) { 827 | unset($data['tags']); 828 | } 829 | if (empty($data['user'])) { 830 | unset($data['user']); 831 | } 832 | if (empty($data['request'])) { 833 | unset($data['request']); 834 | } 835 | 836 | if (!$this->breadcrumbs->is_empty()) { 837 | $data['breadcrumbs'] = $this->breadcrumbs->fetch(); 838 | } 839 | 840 | if ((!$stack && $this->auto_log_stacks) || $stack === true) { 841 | $stack = debug_backtrace(); 842 | 843 | // Drop last stack 844 | array_shift($stack); 845 | } 846 | 847 | if (!empty($stack)) { 848 | // manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149) 849 | if (!class_exists('Raven_Stacktrace')) { 850 | // @codeCoverageIgnoreStart 851 | spl_autoload_call('Raven_Stacktrace'); 852 | // @codeCoverageIgnoreEnd 853 | } 854 | 855 | if (!isset($data['stacktrace']) && !isset($data['exception'])) { 856 | $data['stacktrace'] = array( 857 | 'frames' => Raven_Stacktrace::get_stack_info( 858 | $stack, $this->trace, $vars, $this->message_limit, $this->prefixes, 859 | $this->app_path, $this->excluded_app_paths, $this->serializer, $this->reprSerializer 860 | ), 861 | ); 862 | } 863 | } 864 | 865 | $this->sanitize($data); 866 | $this->process($data); 867 | 868 | if (!$this->store_errors_for_bulk_send) { 869 | $this->send($data); 870 | } else { 871 | $this->_pending_events[] = $data; 872 | } 873 | 874 | $this->_last_event_id = $data['event_id']; 875 | 876 | return $data['event_id']; 877 | } 878 | 879 | public function sanitize(&$data) 880 | { 881 | // attempt to sanitize any user provided data 882 | if (!empty($data['request'])) { 883 | $data['request'] = $this->serializer->serialize($data['request']); 884 | } 885 | if (!empty($data['user'])) { 886 | $data['user'] = $this->serializer->serialize($data['user'], 3); 887 | } 888 | if (!empty($data['extra'])) { 889 | $data['extra'] = $this->serializer->serialize($data['extra']); 890 | } 891 | if (!empty($data['tags'])) { 892 | foreach ($data['tags'] as $key => $value) { 893 | $data['tags'][$key] = @(string)$value; 894 | } 895 | } 896 | if (!empty($data['contexts'])) { 897 | $data['contexts'] = $this->serializer->serialize($data['contexts'], 5); 898 | } 899 | } 900 | 901 | /** 902 | * Process data through all defined Raven_Processor sub-classes 903 | * 904 | * @param array $data Associative array of data to log 905 | */ 906 | public function process(&$data) 907 | { 908 | foreach ($this->processors as $processor) { 909 | $processor->process($data); 910 | } 911 | } 912 | 913 | public function sendUnsentErrors() 914 | { 915 | foreach ($this->_pending_events as $data) { 916 | $this->send($data); 917 | } 918 | $this->_pending_events = array(); 919 | if ($this->store_errors_for_bulk_send) { 920 | //in case an error occurs after this is called, on shutdown, send any new errors. 921 | $this->store_errors_for_bulk_send = !defined('RAVEN_CLIENT_END_REACHED'); 922 | } 923 | } 924 | 925 | /** 926 | * @param array $data 927 | * @return string|bool 928 | */ 929 | public function encode(&$data) 930 | { 931 | $message = Raven_Compat::json_encode($data); 932 | if ($message === false) { 933 | if (function_exists('json_last_error_msg')) { 934 | $this->_lasterror = json_last_error_msg(); 935 | } else { 936 | // @codeCoverageIgnoreStart 937 | $this->_lasterror = json_last_error(); 938 | // @codeCoverageIgnoreEnd 939 | } 940 | return false; 941 | } 942 | 943 | if (function_exists("gzcompress")) { 944 | $message = gzcompress($message); 945 | } 946 | 947 | // PHP's builtin curl_* function are happy without this, but the exec method requires it 948 | $message = base64_encode($message); 949 | 950 | return $message; 951 | } 952 | 953 | /** 954 | * Wrapper to handle encoding and sending data to the Sentry API server. 955 | * 956 | * @param array $data Associative array of data to log 957 | */ 958 | public function send(&$data) 959 | { 960 | if (is_callable($this->send_callback) 961 | && call_user_func_array($this->send_callback, array(&$data)) === false 962 | ) { 963 | // if send_callback returns false, end native send 964 | return; 965 | } 966 | 967 | if (!$this->server) { 968 | return; 969 | } 970 | 971 | if ($this->transport) { 972 | call_user_func($this->transport, $this, $data); 973 | return; 974 | } 975 | 976 | // should this event be sampled? 977 | if (rand(1, 100) / 100.0 > $this->sample_rate) { 978 | return; 979 | } 980 | 981 | $message = $this->encode($data); 982 | 983 | $headers = array( 984 | 'User-Agent' => static::getUserAgent(), 985 | 'X-Sentry-Auth' => $this->getAuthHeader(), 986 | 'Content-Type' => 'application/octet-stream' 987 | ); 988 | 989 | $this->send_remote($this->server, $message, $headers); 990 | } 991 | 992 | /** 993 | * Send data to Sentry 994 | * 995 | * @param string $url Full URL to Sentry 996 | * @param array|string $data Associative array of data to log 997 | * @param array $headers Associative array of headers 998 | */ 999 | protected function send_remote($url, $data, $headers = array()) 1000 | { 1001 | $parts = parse_url($url); 1002 | $parts['netloc'] = $parts['host'].(isset($parts['port']) ? ':'.$parts['port'] : null); 1003 | $this->send_http($url, $data, $headers); 1004 | } 1005 | 1006 | protected static function get_default_ca_cert() 1007 | { 1008 | return dirname(__FILE__) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cacert.pem'; 1009 | } 1010 | 1011 | /** 1012 | * @return array 1013 | * @doc http://stackoverflow.com/questions/9062798/php-curl-timeout-is-not-working/9063006#9063006 1014 | */ 1015 | protected function get_curl_options() 1016 | { 1017 | $options = array( 1018 | CURLOPT_VERBOSE => false, 1019 | CURLOPT_SSL_VERIFYHOST => 2, 1020 | CURLOPT_SSL_VERIFYPEER => $this->verify_ssl, 1021 | CURLOPT_CAINFO => $this->ca_cert, 1022 | CURLOPT_USERAGENT => 'sentry-php/' . self::VERSION, 1023 | ); 1024 | if ($this->http_proxy) { 1025 | $options[CURLOPT_PROXY] = $this->http_proxy; 1026 | } 1027 | if ($this->curl_ssl_version) { 1028 | $options[CURLOPT_SSLVERSION] = $this->curl_ssl_version; 1029 | } 1030 | if ($this->curl_ipv4) { 1031 | $options[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4; 1032 | } 1033 | if (defined('CURLOPT_TIMEOUT_MS')) { 1034 | // MS is available in curl >= 7.16.2 1035 | $timeout = max(1, ceil(1000 * $this->timeout)); 1036 | 1037 | // some versions of PHP 5.3 don't have this defined correctly 1038 | if (!defined('CURLOPT_CONNECTTIMEOUT_MS')) { 1039 | //see stackoverflow link in the phpdoc 1040 | define('CURLOPT_CONNECTTIMEOUT_MS', 156); 1041 | } 1042 | 1043 | $options[CURLOPT_CONNECTTIMEOUT_MS] = $timeout; 1044 | $options[CURLOPT_TIMEOUT_MS] = $timeout; 1045 | } else { 1046 | // fall back to the lower-precision timeout. 1047 | $timeout = max(1, ceil($this->timeout)); 1048 | $options[CURLOPT_CONNECTTIMEOUT] = $timeout; 1049 | $options[CURLOPT_TIMEOUT] = $timeout; 1050 | } 1051 | return $options; 1052 | } 1053 | 1054 | /** 1055 | * Send the message over http to the sentry url given 1056 | * 1057 | * @param string $url URL of the Sentry instance to log to 1058 | * @param array|string $data Associative array of data to log 1059 | * @param array $headers Associative array of headers 1060 | */ 1061 | protected function send_http($url, $data, $headers = array()) 1062 | { 1063 | if ($this->curl_method == 'async') { 1064 | $this->_curl_handler->enqueue($url, $data, $headers); 1065 | } elseif ($this->curl_method == 'exec') { 1066 | $this->send_http_asynchronous_curl_exec($url, $data, $headers); 1067 | } else { 1068 | $this->send_http_synchronous($url, $data, $headers); 1069 | } 1070 | } 1071 | 1072 | protected function buildCurlCommand($url, $data, $headers) 1073 | { 1074 | // TODO(dcramer): support ca_cert 1075 | $cmd = $this->curl_path.' -X POST '; 1076 | foreach ($headers as $key => $value) { 1077 | $cmd .= '-H ' . escapeshellarg($key.': '.$value). ' '; 1078 | } 1079 | $cmd .= '-d ' . escapeshellarg($data) . ' '; 1080 | $cmd .= escapeshellarg($url) . ' '; 1081 | $cmd .= '-m 5 '; // 5 second timeout for the whole process (connect + send) 1082 | if (!$this->verify_ssl) { 1083 | $cmd .= '-k '; 1084 | } 1085 | $cmd .= '> /dev/null 2>&1 &'; // ensure exec returns immediately while curl runs in the background 1086 | 1087 | return $cmd; 1088 | } 1089 | 1090 | /** 1091 | * Send the cURL to Sentry asynchronously. No errors will be returned from cURL 1092 | * 1093 | * @param string $url URL of the Sentry instance to log to 1094 | * @param array|string $data Associative array of data to log 1095 | * @param array $headers Associative array of headers 1096 | * @return bool 1097 | */ 1098 | protected function send_http_asynchronous_curl_exec($url, $data, $headers) 1099 | { 1100 | exec($this->buildCurlCommand($url, $data, $headers)); 1101 | return true; // The exec method is just fire and forget, so just assume it always works 1102 | } 1103 | 1104 | /** 1105 | * Send a blocking cURL to Sentry and check for errors from cURL 1106 | * 1107 | * @param string $url URL of the Sentry instance to log to 1108 | * @param array|string $data Associative array of data to log 1109 | * @param array $headers Associative array of headers 1110 | * @return bool 1111 | */ 1112 | protected function send_http_synchronous($url, $data, $headers) 1113 | { 1114 | $new_headers = array(); 1115 | foreach ($headers as $key => $value) { 1116 | array_push($new_headers, $key .': '. $value); 1117 | } 1118 | // XXX(dcramer): Prevent 100-continue response form server (Fixes GH-216) 1119 | $new_headers[] = 'Expect:'; 1120 | 1121 | if (is_null($this->_curl_instance)) { 1122 | $this->_curl_instance = curl_init($url); 1123 | } 1124 | curl_setopt($this->_curl_instance, CURLOPT_POST, 1); 1125 | curl_setopt($this->_curl_instance, CURLOPT_HTTPHEADER, $new_headers); 1126 | curl_setopt($this->_curl_instance, CURLOPT_POSTFIELDS, $data); 1127 | curl_setopt($this->_curl_instance, CURLOPT_RETURNTRANSFER, true); 1128 | 1129 | $options = $this->get_curl_options(); 1130 | if (isset($options[CURLOPT_CAINFO])) { 1131 | $ca_cert = $options[CURLOPT_CAINFO]; 1132 | unset($options[CURLOPT_CAINFO]); 1133 | } else { 1134 | $ca_cert = null; 1135 | } 1136 | curl_setopt_array($this->_curl_instance, $options); 1137 | 1138 | $buffer = curl_exec($this->_curl_instance); 1139 | 1140 | $errno = curl_errno($this->_curl_instance); 1141 | // CURLE_SSL_CACERT || CURLE_SSL_CACERT_BADFILE 1142 | if ((($errno == 60) || ($errno == 77)) && !is_null($ca_cert)) { 1143 | curl_setopt($this->_curl_instance, CURLOPT_CAINFO, $ca_cert); 1144 | $buffer = curl_exec($this->_curl_instance); 1145 | } 1146 | if ($errno != 0) { 1147 | $this->_lasterror = curl_error($this->_curl_instance); 1148 | $this->_last_sentry_error = null; 1149 | return false; 1150 | } 1151 | 1152 | $code = curl_getinfo($this->_curl_instance, CURLINFO_HTTP_CODE); 1153 | $success = ($code == 200); 1154 | if ($success) { 1155 | $this->_lasterror = null; 1156 | $this->_last_sentry_error = null; 1157 | } else { 1158 | // It'd be nice just to raise an exception here, but it's not very PHP-like 1159 | $this->_lasterror = curl_error($this->_curl_instance); 1160 | $this->_last_sentry_error = @json_decode($buffer); 1161 | } 1162 | 1163 | return $success; 1164 | } 1165 | 1166 | /** 1167 | * Generate a Sentry authorization header string 1168 | * 1169 | * @param string $timestamp Timestamp when the event occurred 1170 | * @param string $client HTTP client name (not Raven_Client object) 1171 | * @param string $api_key Sentry API key 1172 | * @param string $secret_key Sentry API key 1173 | * @return string 1174 | */ 1175 | protected static function get_auth_header($timestamp, $client, $api_key, $secret_key) 1176 | { 1177 | $header = array( 1178 | sprintf('sentry_timestamp=%F', $timestamp), 1179 | "sentry_client={$client}", 1180 | sprintf('sentry_version=%s', self::PROTOCOL), 1181 | ); 1182 | 1183 | if ($api_key) { 1184 | $header[] = "sentry_key={$api_key}"; 1185 | } 1186 | 1187 | if ($secret_key) { 1188 | $header[] = "sentry_secret={$secret_key}"; 1189 | } 1190 | 1191 | 1192 | return sprintf('Sentry %s', implode(', ', $header)); 1193 | } 1194 | 1195 | public function getAuthHeader() 1196 | { 1197 | $timestamp = microtime(true); 1198 | return $this->get_auth_header( 1199 | $timestamp, static::getUserAgent(), $this->public_key, $this->secret_key 1200 | ); 1201 | } 1202 | 1203 | /** 1204 | * Generate an uuid4 value 1205 | * 1206 | * @return string 1207 | */ 1208 | protected static function uuid4() 1209 | { 1210 | $uuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', 1211 | // 32 bits for "time_low" 1212 | mt_rand(0, 0xffff), mt_rand(0, 0xffff), 1213 | 1214 | // 16 bits for "time_mid" 1215 | mt_rand(0, 0xffff), 1216 | 1217 | // 16 bits for "time_hi_and_version", 1218 | // four most significant bits holds version number 4 1219 | mt_rand(0, 0x0fff) | 0x4000, 1220 | 1221 | // 16 bits, 8 bits for "clk_seq_hi_res", 1222 | // 8 bits for "clk_seq_low", 1223 | // two most significant bits holds zero and one for variant DCE1.1 1224 | mt_rand(0, 0x3fff) | 0x8000, 1225 | 1226 | // 48 bits for "node" 1227 | mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) 1228 | ); 1229 | 1230 | return str_replace('-', '', $uuid); 1231 | } 1232 | 1233 | /** 1234 | * Return the URL for the current request 1235 | * 1236 | * @return string|null 1237 | */ 1238 | protected function get_current_url() 1239 | { 1240 | // When running from commandline the REQUEST_URI is missing. 1241 | if (!isset($_SERVER['REQUEST_URI'])) { 1242 | return null; 1243 | } 1244 | 1245 | // HTTP_HOST is a client-supplied header that is optional in HTTP 1.0 1246 | $host = (!empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] 1247 | : (!empty($_SERVER['LOCAL_ADDR']) ? $_SERVER['LOCAL_ADDR'] 1248 | : (!empty($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : ''))); 1249 | 1250 | $httpS = $this->isHttps() ? 's' : ''; 1251 | return "http{$httpS}://{$host}{$_SERVER['REQUEST_URI']}"; 1252 | } 1253 | 1254 | /** 1255 | * Was the current request made over https? 1256 | * 1257 | * @return bool 1258 | */ 1259 | protected function isHttps() 1260 | { 1261 | if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') { 1262 | return true; 1263 | } 1264 | 1265 | if (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) { 1266 | return true; 1267 | } 1268 | 1269 | if (!empty($this->trust_x_forwarded_proto) && 1270 | !empty($_SERVER['X-FORWARDED-PROTO']) && 1271 | $_SERVER['X-FORWARDED-PROTO'] === 'https') { 1272 | return true; 1273 | } 1274 | 1275 | return false; 1276 | } 1277 | 1278 | /** 1279 | * Get the value of a key from $_SERVER 1280 | * 1281 | * @param string $key Key whose value you wish to obtain 1282 | * @return string Key's value 1283 | */ 1284 | private static function _server_variable($key) 1285 | { 1286 | if (isset($_SERVER[$key])) { 1287 | return $_SERVER[$key]; 1288 | } 1289 | 1290 | return ''; 1291 | } 1292 | 1293 | /** 1294 | * Translate a PHP Error constant into a Sentry log level group 1295 | * 1296 | * @param string $severity PHP E_$x error constant 1297 | * @return string Sentry log level group 1298 | */ 1299 | public function translateSeverity($severity) 1300 | { 1301 | if (is_array($this->severity_map) && isset($this->severity_map[$severity])) { 1302 | return $this->severity_map[$severity]; 1303 | } 1304 | switch ($severity) { 1305 | case E_ERROR: return Raven_Client::ERROR; 1306 | case E_WARNING: return Raven_Client::WARN; 1307 | case E_PARSE: return Raven_Client::ERROR; 1308 | case E_NOTICE: return Raven_Client::INFO; 1309 | case E_CORE_ERROR: return Raven_Client::ERROR; 1310 | case E_CORE_WARNING: return Raven_Client::WARN; 1311 | case E_COMPILE_ERROR: return Raven_Client::ERROR; 1312 | case E_COMPILE_WARNING: return Raven_Client::WARN; 1313 | case E_USER_ERROR: return Raven_Client::ERROR; 1314 | case E_USER_WARNING: return Raven_Client::WARN; 1315 | case E_USER_NOTICE: return Raven_Client::INFO; 1316 | case E_STRICT: return Raven_Client::INFO; 1317 | case E_RECOVERABLE_ERROR: return Raven_Client::ERROR; 1318 | } 1319 | if (version_compare(PHP_VERSION, '5.3.0', '>=')) { 1320 | switch ($severity) { 1321 | case E_DEPRECATED: return Raven_Client::WARN; 1322 | case E_USER_DEPRECATED: return Raven_Client::WARN; 1323 | } 1324 | } 1325 | return Raven_Client::ERROR; 1326 | } 1327 | 1328 | /** 1329 | * Provide a map of PHP Error constants to Sentry logging groups to use instead 1330 | * of the defaults in translateSeverity() 1331 | * 1332 | * @param array $map 1333 | */ 1334 | public function registerSeverityMap($map) 1335 | { 1336 | $this->severity_map = $map; 1337 | } 1338 | 1339 | /** 1340 | * Convenience function for setting a user's ID and Email 1341 | * 1342 | * @deprecated 1343 | * @param string $id User's ID 1344 | * @param string|null $email User's email 1345 | * @param array $data Additional user data 1346 | * @codeCoverageIgnore 1347 | */ 1348 | public function set_user_data($id, $email = null, $data = array()) 1349 | { 1350 | $user = array('id' => $id); 1351 | if (isset($email)) { 1352 | $user['email'] = $email; 1353 | } 1354 | $this->user_context(array_merge($user, $data)); 1355 | } 1356 | 1357 | public function onShutdown() 1358 | { 1359 | if (!defined('RAVEN_CLIENT_END_REACHED')) { 1360 | define('RAVEN_CLIENT_END_REACHED', true); 1361 | } 1362 | $this->sendUnsentErrors(); 1363 | if ($this->curl_method == 'async') { 1364 | $this->_curl_handler->join(); 1365 | } 1366 | } 1367 | 1368 | /** 1369 | * Sets user context. 1370 | * 1371 | * @param array $data Associative array of user data 1372 | * @param bool $merge Merge existing context with new context 1373 | */ 1374 | public function user_context($data, $merge = true) 1375 | { 1376 | if ($merge && $this->context->user !== null) { 1377 | // bail if data is null 1378 | if (!$data) { 1379 | return; 1380 | } 1381 | $this->context->user = array_merge($this->context->user, $data); 1382 | } else { 1383 | $this->context->user = $data; 1384 | } 1385 | } 1386 | 1387 | /** 1388 | * Appends tags context. 1389 | * 1390 | * @param array $data Associative array of tags 1391 | */ 1392 | public function tags_context($data) 1393 | { 1394 | $this->context->tags = array_merge($this->context->tags, $data); 1395 | } 1396 | 1397 | /** 1398 | * Appends additional context. 1399 | * 1400 | * @param array $data Associative array of extra data 1401 | */ 1402 | public function extra_context($data) 1403 | { 1404 | $this->context->extra = array_merge($this->context->extra, $data); 1405 | } 1406 | 1407 | /** 1408 | * @param array $processors 1409 | */ 1410 | public function setProcessors(array $processors) 1411 | { 1412 | $this->processors = $processors; 1413 | } 1414 | 1415 | /** 1416 | * @return object|null 1417 | */ 1418 | public function getLastSentryError() 1419 | { 1420 | return $this->_last_sentry_error; 1421 | } 1422 | 1423 | /** 1424 | * @return bool 1425 | */ 1426 | public function getShutdownFunctionHasBeenSet() 1427 | { 1428 | return $this->_shutdown_function_has_been_set; 1429 | } 1430 | 1431 | public function close_curl_resource() 1432 | { 1433 | if (!is_null($this->_curl_instance)) { 1434 | curl_close($this->_curl_instance); 1435 | $this->_curl_instance = null; 1436 | } 1437 | } 1438 | } 1439 | -------------------------------------------------------------------------------- /lib/sentry/sentry/lib/Raven/Compat.php: -------------------------------------------------------------------------------- 1 | $size) { 56 | $key = str_pad(pack($pack, $algo($key)), $size, chr(0x00)); 57 | } else { 58 | $key = str_pad($key, $size, chr(0x00)); 59 | } 60 | 61 | $keyLastPos = strlen($key) - 1; 62 | for ($i = 0; $i < $keyLastPos; $i++) { 63 | $opad[$i] = $opad[$i] ^ $key[$i]; 64 | $ipad[$i] = $ipad[$i] ^ $key[$i]; 65 | } 66 | 67 | $output = $algo($opad.pack($pack, $algo($ipad.$data))); 68 | 69 | return ($raw_output) ? pack($pack, $output) : $output; 70 | } 71 | 72 | /** 73 | * Note that we discard the options given to be compatible 74 | * with PHP < 5.3 75 | * 76 | * @param mixed $value 77 | * @param int $options 78 | * @param int $depth Set the maximum depth 79 | * @return string 80 | */ 81 | public static function json_encode($value, $options = 0, $depth = 512) 82 | { 83 | if (function_exists('json_encode')) { 84 | if (version_compare(PHP_VERSION, '5.3.0', '<')) { 85 | return json_encode($value); 86 | } elseif (version_compare(PHP_VERSION, '5.5.0', '<')) { 87 | return json_encode($value, $options); 88 | } else { 89 | return json_encode($value, $options, $depth); 90 | } 91 | } 92 | 93 | // @codeCoverageIgnoreStart 94 | return self::_json_encode($value, $depth); 95 | // @codeCoverageIgnoreEnd 96 | } 97 | 98 | /** 99 | * @param mixed $value 100 | * @param int $depth Set the maximum depth 101 | * @return string|false 102 | */ 103 | public static function _json_encode($value, $depth = 513) 104 | { 105 | if (ini_get('xdebug.extended_info') !== false) { 106 | ini_set('xdebug.max_nesting_level', 2048); 107 | } 108 | return self::_json_encode_lowlevel($value, $depth); 109 | } 110 | 111 | /** 112 | * Implementation taken from 113 | * http://www.mike-griffiths.co.uk/php-json_encode-alternative/ 114 | * 115 | * @param mixed $value 116 | * @param int $depth Set the maximum depth 117 | * @return string|false 118 | */ 119 | private static function _json_encode_lowlevel($value, $depth) 120 | { 121 | static $jsonReplaces = array( 122 | array('\\', '/', "\n", "\t", "\r", "\f", '"'), 123 | array('\\\\', '\\/', '\\n', '\\t', '\\r', '\\f', '\"')); 124 | 125 | if (is_null($value)) { 126 | return 'null'; 127 | } 128 | if ($value === false) { 129 | return 'false'; 130 | } 131 | if ($value === true) { 132 | return 'true'; 133 | } 134 | 135 | if (is_scalar($value)) { 136 | // Always use '.' for floats. 137 | if (is_float($value)) { 138 | return floatval(str_replace(',', '.', strval($value))); 139 | } 140 | if (is_string($value)) { 141 | return sprintf('"%s"', 142 | str_replace($jsonReplaces[0], $jsonReplaces[1], $value)); 143 | } else { 144 | return $value; 145 | } 146 | } elseif ($depth <= 1) { 147 | return false; 148 | } 149 | 150 | $isList = true; 151 | for ($i = 0, reset($value); $i $v) { 170 | $this_value = self::_json_encode($v, $depth - 1); 171 | if ($this_value === false) { 172 | return false; 173 | } 174 | $result[] = self::_json_encode($k, $depth - 1).':'.$this_value; 175 | } 176 | 177 | return '{' . join(',', $result) . '}'; 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /lib/sentry/sentry/lib/Raven/Context.php: -------------------------------------------------------------------------------- 1 | clear(); 25 | } 26 | 27 | /** 28 | * Clean up existing context. 29 | */ 30 | public function clear() 31 | { 32 | $this->tags = array(); 33 | $this->extra = array(); 34 | $this->user = null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/sentry/sentry/lib/Raven/CurlHandler.php: -------------------------------------------------------------------------------- 1 | options = $options; 28 | $this->multi_handle = curl_multi_init(); 29 | $this->requests = array(); 30 | $this->join_timeout = 5; 31 | 32 | register_shutdown_function(array($this, 'join')); 33 | } 34 | 35 | public function __destruct() 36 | { 37 | $this->join(); 38 | } 39 | 40 | public function enqueue($url, $data = null, $headers = array()) 41 | { 42 | $ch = curl_init(); 43 | 44 | $new_headers = array(); 45 | foreach ($headers as $key => $value) { 46 | array_push($new_headers, $key .': '. $value); 47 | } 48 | // XXX(dcramer): Prevent 100-continue response form server (Fixes GH-216) 49 | $new_headers[] = 'Expect:'; 50 | 51 | curl_setopt($ch, CURLOPT_HTTPHEADER, $new_headers); 52 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 53 | curl_setopt($ch, CURLOPT_URL, $url); 54 | 55 | curl_setopt_array($ch, $this->options); 56 | 57 | if (isset($data)) { 58 | curl_setopt($ch, CURLOPT_POST, true); 59 | curl_setopt($ch, CURLOPT_POSTFIELDS, $data); 60 | } 61 | 62 | curl_multi_add_handle($this->multi_handle, $ch); 63 | 64 | $fd = (int)$ch; 65 | $this->requests[$fd] = 1; 66 | 67 | $this->select(); 68 | 69 | return $fd; 70 | } 71 | 72 | public function join($timeout = null) 73 | { 74 | if (!isset($timeout)) { 75 | $timeout = $this->join_timeout; 76 | } 77 | $start = time(); 78 | do { 79 | $this->select(); 80 | if (count($this->requests) === 0) { 81 | break; 82 | } 83 | usleep(10000); 84 | } while ($timeout !== 0 && time() - $start < $timeout); 85 | } 86 | 87 | /** 88 | * @doc http://php.net/manual/en/function.curl-multi-exec.php 89 | */ 90 | protected function select() 91 | { 92 | do { 93 | $mrc = curl_multi_exec($this->multi_handle, $active); 94 | } while ($mrc == CURLM_CALL_MULTI_PERFORM); 95 | 96 | while ($active && $mrc == CURLM_OK) { 97 | if (curl_multi_select($this->multi_handle) !== -1) { 98 | do { 99 | $mrc = curl_multi_exec($this->multi_handle, $active); 100 | } while ($mrc == CURLM_CALL_MULTI_PERFORM); 101 | } else { 102 | return; 103 | } 104 | } 105 | 106 | while ($info = curl_multi_info_read($this->multi_handle)) { 107 | $ch = $info['handle']; 108 | $fd = (int)$ch; 109 | 110 | curl_multi_remove_handle($this->multi_handle, $ch); 111 | 112 | if (!isset($this->requests[$fd])) { 113 | return; 114 | } 115 | 116 | unset($this->requests[$fd]); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /lib/sentry/sentry/lib/Raven/ErrorHandler.php: -------------------------------------------------------------------------------- 1 | registerExceptionHandler(); 18 | * $error_handler->registerErrorHandler(); 19 | * $error_handler->registerShutdownFunction(); 20 | * 21 | * @package raven 22 | */ 23 | 24 | // TODO(dcramer): deprecate default error types in favor of runtime configuration 25 | // unless a reason can be determined that making them dynamic is better. They 26 | // currently are not used outside of the fatal handler. 27 | class Raven_ErrorHandler 28 | { 29 | protected $old_exception_handler; 30 | protected $call_existing_exception_handler = false; 31 | protected $old_error_handler; 32 | protected $call_existing_error_handler = false; 33 | protected $reservedMemory; 34 | /** @var Raven_Client */ 35 | protected $client; 36 | protected $send_errors_last = false; 37 | protected $fatal_error_types = array( 38 | E_ERROR, 39 | E_PARSE, 40 | E_CORE_ERROR, 41 | E_CORE_WARNING, 42 | E_COMPILE_ERROR, 43 | E_COMPILE_WARNING, 44 | E_STRICT, 45 | ); 46 | 47 | /** 48 | * @var array 49 | * Error types which should be processed by the handler. 50 | * A 'null' value implies "whatever error_reporting is at time of error". 51 | */ 52 | protected $error_types = null; 53 | 54 | public function __construct($client, $send_errors_last = false, $error_types = null, 55 | $__error_types = null) 56 | { 57 | // support legacy fourth argument for error types 58 | if ($error_types === null) { 59 | $error_types = $__error_types; 60 | } 61 | 62 | $this->client = $client; 63 | $this->error_types = $error_types; 64 | $this->fatal_error_types = array_reduce($this->fatal_error_types, array($this, 'bitwiseOr')); 65 | if ($send_errors_last) { 66 | $this->send_errors_last = true; 67 | $this->client->store_errors_for_bulk_send = true; 68 | } 69 | } 70 | 71 | public function bitwiseOr($a, $b) 72 | { 73 | return $a | $b; 74 | } 75 | 76 | public function handleException($e, $isError = false, $vars = null) 77 | { 78 | $e->event_id = $this->client->captureException($e, null, null, $vars); 79 | 80 | if (!$isError && $this->call_existing_exception_handler) { 81 | if ($this->old_exception_handler !== null) { 82 | call_user_func($this->old_exception_handler, $e); 83 | } else { 84 | throw $e; 85 | } 86 | } 87 | } 88 | 89 | public function handleError($type, $message, $file = '', $line = 0, $context = array()) 90 | { 91 | // http://php.net/set_error_handler 92 | // The following error types cannot be handled with a user defined function: E_ERROR, 93 | // E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and 94 | // most of E_STRICT raised in the file where set_error_handler() is called. 95 | 96 | if (error_reporting() !== 0) { 97 | $error_types = $this->error_types; 98 | if ($error_types === null) { 99 | $error_types = error_reporting(); 100 | } 101 | if ($error_types & $type) { 102 | $e = new ErrorException($message, 0, $type, $file, $line); 103 | $this->handleException($e, true, $context); 104 | } 105 | } 106 | 107 | if ($this->call_existing_error_handler) { 108 | if ($this->old_error_handler !== null) { 109 | return call_user_func( 110 | $this->old_error_handler, 111 | $type, 112 | $message, 113 | $file, 114 | $line, 115 | $context 116 | ); 117 | } else { 118 | return false; 119 | } 120 | } 121 | return true; 122 | } 123 | 124 | public function handleFatalError() 125 | { 126 | unset($this->reservedMemory); 127 | 128 | if (null === $error = error_get_last()) { 129 | return; 130 | } 131 | 132 | if ($this->shouldCaptureFatalError($error['type'])) { 133 | $e = new ErrorException( 134 | @$error['message'], 0, @$error['type'], 135 | @$error['file'], @$error['line'] 136 | ); 137 | $this->handleException($e, true); 138 | } 139 | } 140 | 141 | public function shouldCaptureFatalError($type) 142 | { 143 | return $type & $this->fatal_error_types; 144 | } 145 | 146 | /** 147 | * Register a handler which will intercept unhandled exceptions and report them to the 148 | * associated Sentry client. 149 | * 150 | * @param bool $call_existing Call any existing exception handlers after processing 151 | * this instance. 152 | * @return Raven_ErrorHandler 153 | */ 154 | public function registerExceptionHandler($call_existing = true) 155 | { 156 | $this->old_exception_handler = set_exception_handler(array($this, 'handleException')); 157 | $this->call_existing_exception_handler = $call_existing; 158 | return $this; 159 | } 160 | 161 | /** 162 | * Register a handler which will intercept standard PHP errors and report them to the 163 | * associated Sentry client. 164 | * 165 | * @param bool $call_existing Call any existing errors handlers after processing 166 | * this instance. 167 | * @param array $error_types All error types that should be sent. 168 | * @return Raven_ErrorHandler 169 | */ 170 | public function registerErrorHandler($call_existing = true, $error_types = null) 171 | { 172 | if ($error_types !== null) { 173 | $this->error_types = $error_types; 174 | } 175 | $this->old_error_handler = set_error_handler(array($this, 'handleError'), E_ALL); 176 | $this->call_existing_error_handler = $call_existing; 177 | return $this; 178 | } 179 | 180 | /** 181 | * Register a fatal error handler, which will attempt to capture errors which 182 | * shutdown the PHP process. These are commonly things like OOM or timeouts. 183 | * 184 | * @param int $reservedMemorySize Number of kilobytes memory space to reserve, 185 | * which is utilized when handling fatal errors. 186 | * @return Raven_ErrorHandler 187 | */ 188 | public function registerShutdownFunction($reservedMemorySize = 10) 189 | { 190 | register_shutdown_function(array($this, 'handleFatalError')); 191 | 192 | $this->reservedMemory = str_repeat('x', 1024 * $reservedMemorySize); 193 | return $this; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /lib/sentry/sentry/lib/Raven/Exception.php: -------------------------------------------------------------------------------- 1 | client = $client; 28 | } 29 | 30 | /** 31 | * Override the default processor options 32 | * 33 | * @param array $options Associative array of processor options 34 | */ 35 | public function setProcessorOptions(array $options) 36 | { 37 | } 38 | 39 | /** 40 | * Process and sanitize data, modifying the existing value if necessary. 41 | * 42 | * @param array $data Array of log data 43 | */ 44 | abstract public function process(&$data); 45 | } 46 | -------------------------------------------------------------------------------- /lib/sentry/sentry/lib/Raven/Processor/RemoveCookiesProcessor.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | final class Raven_Processor_RemoveCookiesProcessor extends Raven_Processor 19 | { 20 | /** 21 | * {@inheritdoc} 22 | */ 23 | public function process(&$data) 24 | { 25 | if (isset($data['request'])) { 26 | if (isset($data['request']['cookies'])) { 27 | $data['request']['cookies'] = self::STRING_MASK; 28 | } 29 | 30 | if (isset($data['request']['headers']) && isset($data['request']['headers']['Cookie'])) { 31 | $data['request']['headers']['Cookie'] = self::STRING_MASK; 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/sentry/sentry/lib/Raven/Processor/RemoveHttpBodyProcessor.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | final class Raven_Processor_RemoveHttpBodyProcessor extends Raven_Processor 20 | { 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | public function process(&$data) 25 | { 26 | if (isset($data['request'], $data['request']['method']) && in_array(strtoupper($data['request']['method']), array('POST', 'PUT', 'PATCH', 'DELETE'))) { 27 | $data['request']['data'] = self::STRING_MASK; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/sentry/sentry/lib/Raven/Processor/SanitizeDataProcessor.php: -------------------------------------------------------------------------------- 1 | fields_re = self::FIELDS_RE; 36 | $this->values_re = self::VALUES_RE; 37 | $this->session_cookie_name = ini_get('session.name'); 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function setProcessorOptions(array $options) 44 | { 45 | if (isset($options['fields_re'])) { 46 | $this->fields_re = $options['fields_re']; 47 | } 48 | 49 | if (isset($options['values_re'])) { 50 | $this->values_re = $options['values_re']; 51 | } 52 | } 53 | 54 | /** 55 | * Replace any array values with our mask if the field name or the value matches a respective regex 56 | * 57 | * @param mixed $item Associative array value 58 | * @param string $key Associative array key 59 | */ 60 | public function sanitize(&$item, $key) 61 | { 62 | if (empty($item)) { 63 | return; 64 | } 65 | 66 | if (preg_match($this->values_re, $item)) { 67 | $item = self::STRING_MASK; 68 | } 69 | 70 | if (empty($key)) { 71 | return; 72 | } 73 | 74 | if (preg_match($this->fields_re, $key)) { 75 | $item = self::STRING_MASK; 76 | } 77 | } 78 | 79 | public function sanitizeException(&$data) 80 | { 81 | foreach ($data['exception']['values'] as &$value) { 82 | $this->sanitizeStacktrace($value['stacktrace']); 83 | } 84 | } 85 | 86 | public function sanitizeHttp(&$data) 87 | { 88 | $http = &$data['request']; 89 | if (!empty($http['cookies']) && is_array($http['cookies'])) { 90 | $cookies = &$http['cookies']; 91 | if (!empty($cookies[$this->session_cookie_name])) { 92 | $cookies[$this->session_cookie_name] = self::STRING_MASK; 93 | } 94 | } 95 | if (!empty($http['data']) && is_array($http['data'])) { 96 | array_walk_recursive($http['data'], array($this, 'sanitize')); 97 | } 98 | } 99 | 100 | public function sanitizeStacktrace(&$data) 101 | { 102 | foreach ($data['frames'] as &$frame) { 103 | if (empty($frame['vars'])) { 104 | continue; 105 | } 106 | array_walk_recursive($frame['vars'], array($this, 'sanitize')); 107 | } 108 | } 109 | 110 | /** 111 | * {@inheritdoc} 112 | */ 113 | public function process(&$data) 114 | { 115 | if (!empty($data['exception'])) { 116 | $this->sanitizeException($data); 117 | } 118 | if (!empty($data['stacktrace'])) { 119 | $this->sanitizeStacktrace($data['stacktrace']); 120 | } 121 | if (!empty($data['request'])) { 122 | $this->sanitizeHttp($data); 123 | } 124 | if (!empty($data['extra'])) { 125 | array_walk_recursive($data['extra'], array($this, 'sanitize')); 126 | } 127 | } 128 | 129 | /** 130 | * @return string 131 | */ 132 | public function getFieldsRe() 133 | { 134 | return $this->fields_re; 135 | } 136 | 137 | /** 138 | * @param string $fields_re 139 | */ 140 | public function setFieldsRe($fields_re) 141 | { 142 | $this->fields_re = $fields_re; 143 | } 144 | 145 | /** 146 | * @return string 147 | */ 148 | public function getValuesRe() 149 | { 150 | return $this->values_re; 151 | } 152 | 153 | /** 154 | * @param string $values_re 155 | */ 156 | public function setValuesRe($values_re) 157 | { 158 | $this->values_re = $values_re; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /lib/sentry/sentry/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | final class Raven_Processor_SanitizeHttpHeadersProcessor extends Raven_Processor 19 | { 20 | /** 21 | * @var string[] $httpHeadersToSanitize The list of HTTP headers to sanitize 22 | */ 23 | private $httpHeadersToSanitize = array(); 24 | 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | public function __construct(Raven_Client $client) 29 | { 30 | parent::__construct($client); 31 | } 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function setProcessorOptions(array $options) 37 | { 38 | $this->httpHeadersToSanitize = array_merge($this->getDefaultHeaders(), isset($options['sanitize_http_headers']) ? $options['sanitize_http_headers'] : array()); 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function process(&$data) 45 | { 46 | if (isset($data['request']) && isset($data['request']['headers'])) { 47 | foreach ($data['request']['headers'] as $header => &$value) { 48 | if (in_array($header, $this->httpHeadersToSanitize)) { 49 | $value = self::STRING_MASK; 50 | } 51 | } 52 | } 53 | } 54 | 55 | /** 56 | * Gets the list of default headers that must be sanitized. 57 | * 58 | * @return string[] 59 | */ 60 | private function getDefaultHeaders() 61 | { 62 | return array('Authorization', 'Proxy-Authorization', 'X-Csrf-Token', 'X-CSRFToken', 'X-XSRF-TOKEN'); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/sentry/sentry/lib/Raven/Processor/SanitizeStacktraceProcessor.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class Raven_Processor_SanitizeStacktraceProcessor extends Raven_Processor 19 | { 20 | /** 21 | * {@inheritdoc} 22 | */ 23 | public function process(&$data) 24 | { 25 | if (!isset($data['exception'], $data['exception']['values'])) { 26 | return; 27 | } 28 | 29 | foreach ($data['exception']['values'] as &$exception) { 30 | if (!isset($exception['stacktrace'])) { 31 | continue; 32 | } 33 | 34 | foreach ($exception['stacktrace']['frames'] as &$frame) { 35 | unset($frame['pre_context'], $frame['context_line'], $frame['post_context']); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/sentry/sentry/lib/Raven/ReprSerializer.php: -------------------------------------------------------------------------------- 1 | serializeString($value); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/sentry/sentry/lib/Raven/SanitizeDataProcessor.php: -------------------------------------------------------------------------------- 1 | mb_detect_order = $mb_detect_order; 55 | } 56 | } 57 | 58 | /** 59 | * Serialize an object (recursively) into something safe for data 60 | * sanitization and encoding. 61 | * 62 | * @param mixed $value 63 | * @param int $max_depth 64 | * @param int $_depth 65 | * @return string|bool|double|int|null|object|array 66 | */ 67 | public function serialize($value, $max_depth = 3, $_depth = 0) 68 | { 69 | $className = is_object($value) ? get_class($value) : null; 70 | $toArray = is_array($value) || $className === 'stdClass'; 71 | if ($toArray && $_depth < $max_depth) { 72 | $new = array(); 73 | foreach ($value as $k => $v) { 74 | $new[$this->serializeValue($k)] = $this->serialize($v, $max_depth, $_depth + 1); 75 | } 76 | 77 | return $new; 78 | } 79 | return $this->serializeValue($value); 80 | } 81 | 82 | protected function serializeString($value) 83 | { 84 | $value = (string) $value; 85 | if (function_exists('mb_detect_encoding') 86 | && function_exists('mb_convert_encoding') 87 | ) { 88 | // we always guarantee this is coerced, even if we can't detect encoding 89 | if ($currentEncoding = mb_detect_encoding($value, $this->mb_detect_order)) { 90 | $value = mb_convert_encoding($value, 'UTF-8', $currentEncoding); 91 | } else { 92 | $value = mb_convert_encoding($value, 'UTF-8'); 93 | } 94 | } 95 | 96 | if (strlen($value) > 1024) { 97 | $value = substr($value, 0, 1014) . ' {clipped}'; 98 | } 99 | 100 | return $value; 101 | } 102 | 103 | /** 104 | * @param mixed $value 105 | * @return string|bool|double|int|null 106 | */ 107 | protected function serializeValue($value) 108 | { 109 | if (is_null($value) || is_bool($value) || is_float($value) || is_integer($value)) { 110 | return $value; 111 | } elseif (is_object($value) || gettype($value) == 'object') { 112 | return 'Object '.get_class($value); 113 | } elseif (is_resource($value)) { 114 | return 'Resource '.get_resource_type($value); 115 | } elseif (is_array($value)) { 116 | return 'Array of length ' . count($value); 117 | } else { 118 | return $this->serializeString($value); 119 | } 120 | } 121 | 122 | 123 | /** 124 | * @return string 125 | * @codeCoverageIgnore 126 | */ 127 | public function getMbDetectOrder() 128 | { 129 | return $this->mb_detect_order; 130 | } 131 | 132 | /** 133 | * @param string $mb_detect_order 134 | * 135 | * @return Raven_Serializer 136 | * @codeCoverageIgnore 137 | */ 138 | public function setMbDetectOrder($mb_detect_order) 139 | { 140 | $this->mb_detect_order = $mb_detect_order; 141 | 142 | return $this; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /lib/sentry/sentry/lib/Raven/Stacktrace.php: -------------------------------------------------------------------------------- 1 | $context['filename'], 73 | 'lineno' => (int) $context['lineno'], 74 | 'function' => isset($nextframe['function']) ? $nextframe['function'] : null, 75 | 'pre_context' => $serializer->serialize($context['prefix']), 76 | 'context_line' => $serializer->serialize($context['line']), 77 | 'post_context' => $serializer->serialize($context['suffix']), 78 | ); 79 | 80 | // detect in_app based on app path 81 | if ($app_path) { 82 | $norm_abs_path = @realpath($abs_path) ?: $abs_path; 83 | $in_app = (bool)(substr($norm_abs_path, 0, strlen($app_path)) === $app_path); 84 | if ($in_app && $excluded_app_paths) { 85 | foreach ($excluded_app_paths as $path) { 86 | if (substr($norm_abs_path, 0, strlen($path)) === $path) { 87 | $in_app = false; 88 | break; 89 | } 90 | } 91 | } 92 | $data['in_app'] = $in_app; 93 | } 94 | 95 | // dont set this as an empty array as PHP will treat it as a numeric array 96 | // instead of a mapping which goes against the defined Sentry spec 97 | if (!empty($vars)) { 98 | $cleanVars = array(); 99 | foreach ($vars as $key => $value) { 100 | $value = $reprSerializer->serialize($value); 101 | if (is_string($value) || is_numeric($value)) { 102 | $cleanVars[(string)$key] = substr($value, 0, $frame_var_limit); 103 | } else { 104 | $cleanVars[(string)$key] = $value; 105 | } 106 | } 107 | $data['vars'] = $cleanVars; 108 | } 109 | 110 | $result[] = $data; 111 | } 112 | 113 | return array_reverse($result); 114 | } 115 | 116 | public static function get_default_context($frame, $frame_arg_limit = Raven_Client::MESSAGE_LIMIT) 117 | { 118 | if (!isset($frame['args'])) { 119 | return array(); 120 | } 121 | 122 | $i = 1; 123 | $args = array(); 124 | foreach ($frame['args'] as $arg) { 125 | $args['param'.$i] = self::serialize_argument($arg, $frame_arg_limit); 126 | $i++; 127 | } 128 | return $args; 129 | } 130 | 131 | public static function get_frame_context($frame, $frame_arg_limit = Raven_Client::MESSAGE_LIMIT) 132 | { 133 | if (!isset($frame['args'])) { 134 | return array(); 135 | } 136 | 137 | // The reflection API seems more appropriate if we associate it with the frame 138 | // where the function is actually called (since we're treating them as function context) 139 | if (!isset($frame['function'])) { 140 | return self::get_default_context($frame, $frame_arg_limit); 141 | } 142 | if (strpos($frame['function'], '__lambda_func') !== false) { 143 | return self::get_default_context($frame, $frame_arg_limit); 144 | } 145 | if (isset($frame['class']) && $frame['class'] == 'Closure') { 146 | return self::get_default_context($frame, $frame_arg_limit); 147 | } 148 | if (strpos($frame['function'], '{closure}') !== false) { 149 | return self::get_default_context($frame, $frame_arg_limit); 150 | } 151 | if (in_array($frame['function'], self::$statements)) { 152 | if (empty($frame['args'])) { 153 | // No arguments 154 | return array(); 155 | } else { 156 | // Sanitize the file path 157 | return array( 158 | 'param1' => self::serialize_argument($frame['args'][0], $frame_arg_limit), 159 | ); 160 | } 161 | } 162 | try { 163 | if (isset($frame['class'])) { 164 | if (method_exists($frame['class'], $frame['function'])) { 165 | $reflection = new ReflectionMethod($frame['class'], $frame['function']); 166 | } elseif ($frame['type'] === '::') { 167 | $reflection = new ReflectionMethod($frame['class'], '__callStatic'); 168 | } else { 169 | $reflection = new ReflectionMethod($frame['class'], '__call'); 170 | } 171 | } else { 172 | $reflection = new ReflectionFunction($frame['function']); 173 | } 174 | } catch (ReflectionException $e) { 175 | return self::get_default_context($frame, $frame_arg_limit); 176 | } 177 | 178 | $params = $reflection->getParameters(); 179 | 180 | $args = array(); 181 | foreach ($frame['args'] as $i => $arg) { 182 | $arg = self::serialize_argument($arg, $frame_arg_limit); 183 | if (isset($params[$i])) { 184 | // Assign the argument by the parameter name 185 | $args[$params[$i]->name] = $arg; 186 | } else { 187 | $args['param'.$i] = $arg; 188 | } 189 | } 190 | 191 | return $args; 192 | } 193 | 194 | private static function serialize_argument($arg, $frame_arg_limit) 195 | { 196 | if (is_array($arg)) { 197 | $_arg = array(); 198 | foreach ($arg as $key => $value) { 199 | if (is_string($value) || is_numeric($value)) { 200 | $_arg[$key] = substr($value, 0, $frame_arg_limit); 201 | } else { 202 | $_arg[$key] = $value; 203 | } 204 | } 205 | return $_arg; 206 | } elseif (is_string($arg) || is_numeric($arg)) { 207 | return substr($arg, 0, $frame_arg_limit); 208 | } else { 209 | return $arg; 210 | } 211 | } 212 | 213 | private static function strip_prefixes($filename, $prefixes) 214 | { 215 | if ($prefixes === null) { 216 | return $filename; 217 | } 218 | foreach ($prefixes as $prefix) { 219 | if (substr($filename, 0, strlen($prefix)) === $prefix) { 220 | return substr($filename, strlen($prefix)); 221 | } 222 | } 223 | return $filename; 224 | } 225 | 226 | private static function read_source_file($filename, $lineno, $context_lines = 5) 227 | { 228 | $frame = array( 229 | 'prefix' => array(), 230 | 'line' => '', 231 | 'suffix' => array(), 232 | 'filename' => $filename, 233 | 'lineno' => $lineno, 234 | ); 235 | 236 | if ($filename === null || $lineno === null) { 237 | return $frame; 238 | } 239 | 240 | // Code which is eval'ed have a modified filename.. Extract the 241 | // correct filename + linenumber from the string. 242 | $matches = array(); 243 | $matched = preg_match("/^(.*?)\\((\\d+)\\) : eval\\(\\)'d code$/", 244 | $filename, $matches); 245 | if ($matched) { 246 | $frame['filename'] = $filename = $matches[1]; 247 | $frame['lineno'] = $lineno = $matches[2]; 248 | } 249 | 250 | // In the case of an anonymous function, the filename is sent as: 251 | // "() : runtime-created function" 252 | // Extract the correct filename + linenumber from the string. 253 | $matches = array(); 254 | $matched = preg_match("/^(.*?)\\((\\d+)\\) : runtime-created function$/", 255 | $filename, $matches); 256 | if ($matched) { 257 | $frame['filename'] = $filename = $matches[1]; 258 | $frame['lineno'] = $lineno = $matches[2]; 259 | } 260 | 261 | try { 262 | $file = new SplFileObject($filename); 263 | $target = max(0, ($lineno - ($context_lines + 1))); 264 | $file->seek($target); 265 | $cur_lineno = $target+1; 266 | while (!$file->eof()) { 267 | $line = rtrim($file->current(), "\r\n"); 268 | if ($cur_lineno == $lineno) { 269 | $frame['line'] = $line; 270 | } elseif ($cur_lineno < $lineno) { 271 | $frame['prefix'][] = $line; 272 | } elseif ($cur_lineno > $lineno) { 273 | $frame['suffix'][] = $line; 274 | } 275 | $cur_lineno++; 276 | if ($cur_lineno > $lineno + $context_lines) { 277 | break; 278 | } 279 | $file->next(); 280 | } 281 | } catch (RuntimeException $exc) { 282 | return $frame; 283 | } 284 | 285 | return $frame; 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /lib/sentry/sentry/lib/Raven/TransactionStack.php: -------------------------------------------------------------------------------- 1 | stack = array(); 16 | } 17 | 18 | public function clear() 19 | { 20 | $this->stack = array(); 21 | } 22 | 23 | public function peek() 24 | { 25 | $len = count($this->stack); 26 | if ($len === 0) { 27 | return null; 28 | } 29 | return $this->stack[$len - 1]; 30 | } 31 | 32 | public function push($context) 33 | { 34 | $this->stack[] = $context; 35 | } 36 | 37 | /** @noinspection PhpInconsistentReturnPointsInspection 38 | * @param string|null $context 39 | * @return mixed 40 | */ 41 | public function pop($context = null) 42 | { 43 | if (!$context) { 44 | return array_pop($this->stack); 45 | } 46 | while (!empty($this->stack)) { 47 | if (array_pop($this->stack) === $context) { 48 | return $context; 49 | } 50 | } 51 | // @codeCoverageIgnoreStart 52 | } 53 | // @codeCoverageIgnoreEnd 54 | } 55 | -------------------------------------------------------------------------------- /lib/sentry/sentry/lib/Raven/Util.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 15 | ./test/Raven/ 16 | 17 | 18 | 19 | 20 | 21 | ./lib/Raven/ 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /modman: -------------------------------------------------------------------------------- 1 | app/code/community/Hackathon/LoggerSentry app/code/community/Hackathon/LoggerSentry 2 | app/etc/modules/Hackathon_LoggerSentry.xml app/etc/modules/Hackathon_LoggerSentry.xml 3 | lib/sentry lib/sentry --------------------------------------------------------------------------------