├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── LICENSE.txt ├── README.md ├── composer.json ├── issue_template.md ├── modman ├── screenshot.png └── src ├── app ├── code │ └── community │ │ └── Bugsnag │ │ └── Notifier │ │ ├── Block │ │ └── Adminhtml │ │ │ └── System │ │ │ └── Config │ │ │ └── Form │ │ │ └── Button.php │ │ ├── Model │ │ ├── Observer.php │ │ └── Severity.php │ │ ├── controllers │ │ └── Adminhtml │ │ │ └── BugsnagController.php │ │ └── etc │ │ ├── config.xml │ │ └── system.xml ├── design │ └── adminhtml │ │ └── default │ │ └── default │ │ └── template │ │ └── bugsnag │ │ └── system │ │ └── config │ │ └── button.phtml └── etc │ └── modules │ └── Bugsnag_Notifier.xml └── lib └── bugsnag-php ├── Autoload.php ├── Client.php ├── Configuration.php ├── Diagnostics.php ├── Error.php ├── ErrorTypes.php ├── Notification.php ├── Request.php └── Stacktrace.php /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Goal 2 | 3 | 12 | 13 | 20 | 21 | ## Changeset 22 | 23 | 32 | 33 | ## Tests 34 | 35 | 37 | 38 | ## Discussion 39 | 40 | ### Alternative Approaches 41 | 42 | 43 | 44 | ### Outstanding Questions 45 | 46 | 49 | 50 | ### Linked issues 51 | 52 | 58 | 59 | ## Review 60 | 61 | 63 | 64 | For the submitter, initial self-review: 65 | 66 | - [ ] Commented on code changes inline explain the reasoning behind the approach 67 | - [ ] Reviewed the test cases added for completeness and possible points for discussion 68 | - [ ] A changelog entry was added for the goal of this pull request 69 | - [ ] Check the scope of the changeset - is everything in the diff required for the pull request? 70 | - This pull request is ready for: 71 | - [ ] Initial review of the intended approach, not yet feature complete 72 | - [ ] Structural review of the classes, functions, and properties modified 73 | - [ ] Final review 74 | 75 | For the pull request reviewer(s), this changeset has been reviewed for: 76 | 77 | - [ ] Consistency across platforms for structures or concepts added or modified 78 | - [ ] Consistency between the changeset and the goal stated above 79 | - [ ] Internal consistency with the rest of the library - is there any overlap between existing interfaces and any which have been added? 80 | - [ ] Usage friction - is the proposed change in usage cumbersome or complicated? 81 | - [ ] Performance and complexity - are there any cases of unexpected O(n^3) when iterating, recursing, flat mapping, etc? 82 | - [ ] Concurrency concerns - if components are accessed asynchronously, what issues will arise 83 | - [ ] Thoroughness of added tests and any missing edge cases 84 | - [ ] Idiomatic use of the language 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | build 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Bugsnag, https://bugsnag.com/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Bugsnag Notifier for Magento 2 | == 3 | 4 | ## Warning: This notifier has been deprecated and will only be receiving critical bug fixes in the future 5 | 6 | The Bugsnag Notifier for Magento gives you instant notification of errors in 7 | your Magento applications. 8 | 9 | [Bugsnag](https://www.bugsnag.com/) captures errors in real-time from your web, mobile and 10 | desktop applications, helping you to understand and resolve them as fast as 11 | possible. [Create a free account](https://www.bugsnag.com/) to start capturing errors from 12 | your applications. 13 | 14 | The Bugsnag Notifier for Magento supports Magento CE 1.9.0.1+ and PHP 5.2+. It 15 | may work on older versions of Magento, but it's untested. 16 | 17 | You can always read about the plugin or download it from Magento Connect. 18 | 19 | Learn more about [monitoring and reporting Magento errors](https://www.bugsnag.com/platforms/php/magento/) with Bugsnag. 20 | 21 | Installation 22 | -- 23 | 24 | ### Magento Connect (recommended way) 25 | 26 | 1. Visit [the module's page on Magento Connect](http://www.magentocommerce.com/magento-connect/catalog/product/view/id/24935/s/bugsnag-notifier/) 27 | 1. Press "Install Now" and and follow the instructions. 28 | 29 | ### Modman 30 | 31 | ``` 32 | cd 33 | modman init 34 | modman clone https://github.com/bugsnag/bugsnag-magento.git 35 | ``` 36 | 37 | ### Composer 38 | 39 | ``` 40 | { 41 | "require": { 42 | "magento/core": "1.9.0.1", 43 | "bugsnag/bugsnag-magento": "dev-master", 44 | "magento-hackathon/magento-composer-installer": "*" 45 | }, 46 | "repositories": [ 47 | { 48 | "type": "composer", 49 | "url": "http://packages.firegento.com" 50 | } 51 | ], 52 | "extra":{ 53 | "magento-root-dir": "htdocs/" 54 | } 55 | } 56 | ``` 57 | 58 | ### Manual 59 | 60 | ``` 61 | git clone https://github.com/bugsnag/bugsnag-magento.git 62 | cp -R bugsnag-magento/src/* 63 | ``` 64 | 65 | Configuration 66 | -- 67 | 68 | In your Magento admin panel go to System → Configuration → Advanced → Developer 69 | and find the Bugsnag entry. If you don't see the entry, just press `Save Config`: 70 | the command would refresh the cache and pick up Bugsnag. 71 | 72 | ![](/screenshot.png) 73 | 74 | If the orange button "Fire Test Event" is not visible, then go to System → 75 | Configuration → Developer → Template Settings and set `Allow Symlinks` to `Yes`. 76 | You can safely set it back to `No` if you don't need the button or if you worry 77 | about the potential security risks (the plugin will keep working). 78 | 79 | To do 80 | -- 81 | 82 | * Magento 2 introduces a lot of 83 | [exciting changes](https://wiki.magento.com/display/MAGE2DOC/Module+Dependency+Declarations). Currently, 84 | we do not support them 85 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bugsnag/bugsnag-magento", 3 | "type": "magento-module", 4 | "description": "Magento extension for bugsnag.com", 5 | "license": "MIT", 6 | "require": { 7 | "bugsnag/bugsnag": "^2.0", 8 | "magento-hackathon/magento-composer-installer": "*" 9 | }, 10 | "extra": { 11 | "magento-root-dir": "." 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /issue_template.md: -------------------------------------------------------------------------------- 1 | ### Expected behavior 2 | *[Insert details on expected behaviour]* 3 | 4 | ### Observed behavior 5 | *[Insert details on observed behaviour]* 6 | 7 | ### Steps to reproduce 8 | *[Insert reproduction steps (if known)]* 9 | 10 | ### Version 11 | *[Insert version information]* 12 | 13 | ### Additional information 14 | *[Insert any additional information]* 15 | 16 | #### Can't comment on Issues? 17 | Some users have been unable to comment on Github issues when an [adblocker extension is enabled](https://docs.bugsnag.com/platforms/browsers/faq/#is-bugsnag-blocked-by-ad-blockers). 18 | We recommend temporarily disabling the extension, or if that fails, contacting support@bugsnag.com. 19 | -------------------------------------------------------------------------------- /modman: -------------------------------------------------------------------------------- 1 | src/app/code/community/Bugsnag/Notifier/ app/code/community/Bugsnag/Notifier/ 2 | src/app/design/adminhtml/default/default/template/bugsnag/system/config/button.phtml app/design/adminhtml/default/default/template/bugsnag/system/config/button.phtml 3 | src/app/etc/modules/Bugsnag_Notifier.xml app/etc/modules/Bugsnag_Notifier.xml 4 | src/lib/bugsnag-php lib/bugsnag-php 5 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bugsnag/bugsnag-magento/11c651abb5faf0bc0712b31a1b84cb94707f3848/screenshot.png -------------------------------------------------------------------------------- /src/app/code/community/Bugsnag/Notifier/Block/Adminhtml/System/Config/Form/Button.php: -------------------------------------------------------------------------------- 1 | setTemplate('bugsnag/system/config/button.phtml'); 9 | } 10 | 11 | protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element) 12 | { 13 | return $this->_toHtml(); 14 | } 15 | 16 | public function getAjaxCheckUrl() 17 | { 18 | return Mage::helper('adminhtml')->getUrl('adminhtml/adminhtml_bugsnag/check'); 19 | } 20 | 21 | public function getButtonHtml() 22 | { 23 | $button = $this 24 | ->getLayout() 25 | ->createBlock('adminhtml/widget_button') 26 | ->setData( 27 | array( 28 | 'type' => 'button', 29 | 'id' => 'Bugsnag_Notifier', 30 | 'label' => $this->helper('adminhtml')->__('Fire Test Event'), 31 | 'onclick' => 'javascript:fireTestEvent(); return false;' 32 | ) 33 | ); 34 | 35 | return $button->toHtml(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/app/code/community/Bugsnag/Notifier/Model/Observer.php: -------------------------------------------------------------------------------- 1 | "Bugsnag Magento (Official)", 9 | "version" => "1.0.0", 10 | "url" => "https://bugsnag.com/notifiers/magento" 11 | ); 12 | 13 | private $client; 14 | private $apiKey; 15 | private $notifySeverities; 16 | private $filterFields; 17 | 18 | public function fireTestEvent($apiKey) { 19 | if (strlen($apiKey) != 32) { 20 | throw new Exception("Invalid length of the API key"); 21 | } 22 | 23 | $client = new Bugsnag_Client($apiKey); 24 | $client->notifyError( 25 | "BugsnagTest", 26 | "Testing bugsnag", 27 | array("notifier" => self::$NOTIFIER) 28 | ); 29 | } 30 | 31 | public function initBugsnag() 32 | { 33 | if (file_exists(Mage::getBaseDir('lib') . '/bugsnag-php/Autoload.php')) { 34 | require_once(Mage::getBaseDir('lib') . '/bugsnag-php/Autoload.php'); 35 | } else { 36 | error_log("Bugsnag Error: Couldn't activate Bugsnag Error Monitoring due to missing Bugsnag PHP library!"); 37 | return; 38 | } 39 | 40 | $this->apiKey = Mage::getStoreConfig("dev/Bugsnag_Notifier/apiKey"); 41 | $this->notifySeverities = Mage::getStoreConfig("dev/Bugsnag_Notifier/severities"); 42 | $this->filterFields = Mage::getStoreConfig("dev/Bugsnag_Notifier/filterFields"); 43 | 44 | // Activate the bugsnag client 45 | if (!empty($this->apiKey)) { 46 | $this->client = new Bugsnag_Client($this->apiKey); 47 | 48 | $this->client->setReleaseStage($this->releaseStage()) 49 | ->setErrorReportingLevel($this->errorReportingLevel()) 50 | ->setFilters($this->filterFields()); 51 | 52 | $this->client->setNotifier(self::$NOTIFIER); 53 | 54 | if (Mage::getSingleton('customer/session')->isLoggedIn()) { 55 | $this->addUserTab(); 56 | } 57 | 58 | set_error_handler(array($this->client, "errorHandler")); 59 | set_exception_handler(array($this->client, "exceptionHandler")); 60 | } 61 | } 62 | 63 | private function addUserTab() 64 | { 65 | $customer = Mage::getSingleton('customer/session')->getCustomer(); 66 | $this->client->setUser(array( 67 | 'id' => $customer->getId(), 68 | 'email' => $customer->getEmail(), 69 | 'created_at' => $customer->getCreatedAt(), 70 | 'first_name' => $customer->getFirstname(), 71 | 'last_name' => $customer->getLastname() 72 | )); 73 | } 74 | 75 | private function releaseStage() 76 | { 77 | return Mage::getIsDeveloperMode() ? "development" : "production"; 78 | } 79 | 80 | private function errorReportingLevel() 81 | { 82 | if (empty($this->notifySeverities)) { 83 | $notifySeverities = "fatal,error"; 84 | } else { 85 | $notifySeverities = $this->notifySeverities; 86 | } 87 | 88 | $level = 0; 89 | $severities = explode(",", $notifySeverities); 90 | 91 | foreach($severities as $severity) { 92 | $level |= Bugsnag_ErrorTypes::getLevelsForSeverity($severity); 93 | } 94 | 95 | return $level; 96 | } 97 | 98 | private function filterFields() 99 | { 100 | return array_map('trim', explode("\n", $this->filterFields)); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/app/code/community/Bugsnag/Notifier/Model/Severity.php: -------------------------------------------------------------------------------- 1 | 'fatal,error', 'label'=>Mage::helper('adminhtml')->__('Crashes & errors')), 9 | array('value' => 'fatal,error,warning', 'label'=>Mage::helper('adminhtml')->__('Crashes, errors & warnings')), 10 | array('value' => 'fatal,error,warning,info', 'label'=>Mage::helper('adminhtml')->__('Crashes, errors, warnings & info messages')) 11 | ); 12 | } 13 | } -------------------------------------------------------------------------------- /src/app/code/community/Bugsnag/Notifier/controllers/Adminhtml/BugsnagController.php: -------------------------------------------------------------------------------- 1 | fireTestEvent($_POST["apiKey"]); 9 | $successCode = 1; 10 | Mage::app()->getResponse()->setBody($successCode); 11 | } 12 | 13 | public function _isAllowed() 14 | { 15 | return true; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/code/community/Bugsnag/Notifier/etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 1.0.0 6 | 7 | 8 | 9 | 10 | 11 | Bugsnag_Notifier_Model 12 | 13 | 14 | 15 | 16 | 17 | 18 | singleton 19 | Bugsnag_Notifier_Model_Observer 20 | initBugsnag 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Bugsnag_Notifier 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/app/code/community/Bugsnag/Notifier/etc/system.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 0 9 | 1 10 | 1 11 | 1 12 | 13 | 14 | 15 | text 16 | 20 17 | 1 18 | 1 19 | 1 20 | 21 | 22 | 23 | select 24 | Bugsnag_Notifier_Model_Severity 25 | 40 26 | 1 27 | 1 28 | 1 29 | 30 | 31 | 37 | textarea 38 | 50 39 | 1 40 | 1 41 | 1 42 | 43 | 44 | 45 | button 46 | Bugsnag_Notifier_Block_Adminhtml_System_Config_Form_Button 47 | 60 48 | 1 49 | 1 50 | 1 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/app/design/adminhtml/default/default/template/bugsnag/system/config/button.phtml: -------------------------------------------------------------------------------- 1 | 20 | 21 | getButtonHtml() ?> 22 | -------------------------------------------------------------------------------- /src/app/etc/modules/Bugsnag_Notifier.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | community 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/lib/bugsnag-php/Autoload.php: -------------------------------------------------------------------------------- 1 | config = new Bugsnag_Configuration(); 24 | $this->config->apiKey = $apiKey; 25 | 26 | // Build a Diagnostics object 27 | $this->diagnostics = new Bugsnag_Diagnostics($this->config); 28 | 29 | // Register a shutdown function to check for fatal errors 30 | // and flush any buffered errors 31 | register_shutdown_function(array($this, 'shutdownHandler')); 32 | } 33 | 34 | /** 35 | * Set your release stage, eg "production" or "development" 36 | * 37 | * @param String $releaseStage the app's current release stage 38 | * @return $this 39 | */ 40 | public function setReleaseStage($releaseStage) 41 | { 42 | $this->config->releaseStage = $releaseStage; 43 | 44 | return $this; 45 | } 46 | 47 | /** 48 | * Set your app's semantic version, eg "1.2.3" 49 | * 50 | * @param String $appVersion the app's version 51 | * @return $this 52 | */ 53 | public function setAppVersion($appVersion) 54 | { 55 | $this->config->appVersion = $appVersion; 56 | 57 | return $this; 58 | } 59 | 60 | /** 61 | * Set the host name 62 | * 63 | * @param String $hostname the host name 64 | * @return $this 65 | */ 66 | public function setHostname($hostname) 67 | { 68 | $this->config->hostname = $hostname; 69 | 70 | return $this; 71 | } 72 | 73 | /** 74 | * Set which release stages should be allowed to notify Bugsnag 75 | * eg array("production", "development") 76 | * 77 | * @param Array $notifyReleaseStages array of release stages to notify for 78 | * @return $this 79 | */ 80 | public function setNotifyReleaseStages(array $notifyReleaseStages) 81 | { 82 | $this->config->notifyReleaseStages = $notifyReleaseStages; 83 | 84 | return $this; 85 | } 86 | 87 | /** 88 | * Set which Bugsnag endpoint to send errors to. 89 | * 90 | * @param String $endpoint endpoint URL 91 | * @return $this 92 | */ 93 | public function setEndpoint($endpoint) 94 | { 95 | $this->config->endpoint = $endpoint; 96 | 97 | return $this; 98 | } 99 | 100 | /** 101 | * Enable debug mode to help diagnose problems. 102 | * 103 | * @param Boolean $debug whether to enable debug mode 104 | * @return $this 105 | */ 106 | public function setDebug($debug) 107 | { 108 | $this->config->debug = $debug; 109 | 110 | return $this; 111 | } 112 | 113 | /** 114 | * Set whether or not to use SSL when notifying bugsnag 115 | * 116 | * @param Boolean $useSSL whether to use SSL 117 | * @deprecated you can now pass full URLs to setEndpoint 118 | * @return $this 119 | */ 120 | public function setUseSSL($useSSL) 121 | { 122 | $this->config->useSSL = $useSSL; 123 | 124 | return $this; 125 | } 126 | 127 | /** 128 | * Set the desired timeout for cURL connection when notifying bugsnag 129 | * 130 | * @param Integer $timeout the desired timeout in seconds 131 | * @return $this 132 | */ 133 | public function setTimeout($timeout) 134 | { 135 | $this->config->timeout = $timeout; 136 | 137 | return $this; 138 | } 139 | 140 | /** 141 | * Set the absolute path to the root of your application. 142 | * We use this to help with error grouping and to highlight "in project" 143 | * stacktrace lines. 144 | * 145 | * @param String $projectRoot the root path for your application 146 | * @return $this 147 | */ 148 | public function setProjectRoot($projectRoot) 149 | { 150 | $this->config->setProjectRoot($projectRoot); 151 | 152 | return $this; 153 | } 154 | 155 | /** 156 | * Set the path that should be stripped from the beginning of 157 | * any stacktrace file line. This helps to normalise filenames 158 | * for grouping and reduces the noise in stack traces. 159 | * 160 | * @param String $stripPath the path to strip from filenames 161 | * @return $this 162 | */ 163 | public function setStripPath($stripPath) 164 | { 165 | $this->config->setStripPath($stripPath); 166 | 167 | return $this; 168 | } 169 | 170 | /** 171 | * Set the a regular expression for matching filenames in stacktrace lines 172 | * that are part of your application. 173 | * 174 | * @param String $projectRootRegex regex matching paths belong to your project 175 | * @return $this 176 | */ 177 | public function setProjectRootRegex($projectRootRegex) 178 | { 179 | $this->config->projectRootRegex = $projectRootRegex; 180 | 181 | return $this; 182 | } 183 | 184 | /** 185 | * Set the strings to filter out from metaData arrays before sending then 186 | * to Bugsnag. Eg. array("password", "credit_card") 187 | * 188 | * @param Array $filters an array of metaData filters 189 | * @return $this 190 | */ 191 | public function setFilters(array $filters) 192 | { 193 | $this->config->filters = $filters; 194 | 195 | return $this; 196 | } 197 | 198 | /** 199 | * Set information about the current user of your app, including 200 | * id, name and email. 201 | * 202 | * @param Array $user an array of user information. Eg: 203 | * array( 204 | * 'name' => 'Bob Hoskins', 205 | * 'email' => 'bob@hoskins.com' 206 | * ) 207 | * @return $this 208 | */ 209 | public function setUser(array $user) 210 | { 211 | $this->config->user = $user; 212 | 213 | return $this; 214 | } 215 | 216 | /** 217 | * @deprecated deprecated since version 2.1 218 | * @param $userId 219 | * @return $this 220 | */ 221 | public function setUserId($userId) 222 | { 223 | if (!is_array($this->config->user)) { 224 | $this->config->user = array(); 225 | } 226 | 227 | $this->config->user['id'] = $userId; 228 | 229 | return $this; 230 | } 231 | 232 | /** 233 | * Set a context representing the current type of request, or location in code. 234 | * 235 | * @param String $context the current context 236 | * @return $this 237 | */ 238 | public function setContext($context) 239 | { 240 | $this->config->context = $context; 241 | 242 | return $this; 243 | } 244 | 245 | /** 246 | * Set the type of application executing the code. This is usually used to 247 | * represent if you are running plain PHP code "php", via a framework, 248 | * eg "laravel", or executing through delayed worker code, eg "resque". 249 | * 250 | * @param String $type the current type 251 | * @return $this 252 | */ 253 | public function setType($type) 254 | { 255 | $this->config->type = $type; 256 | 257 | return $this; 258 | } 259 | 260 | /** 261 | * Set custom metadata to send to Bugsnag with every error. You can use 262 | * this to add custom tabs of data to each error on your Bugsnag dashboard 263 | * 264 | * @param Array $metaData an array of arrays of custom data. Eg: 265 | * array( 266 | * "user" => array( 267 | * "name" => "James", 268 | * "email" => "james@example.com" 269 | * ) 270 | * ) 271 | * @return $this 272 | */ 273 | public function setMetaData(array $metaData) 274 | { 275 | $this->config->metaData = $metaData; 276 | 277 | return $this; 278 | } 279 | 280 | /** 281 | * Set proxy configuration 282 | * 283 | * @param Array $proxySettings an array with proxy settings. Eg: 284 | * array( 285 | * 'host' => "bugsnag.com", 286 | * 'port' => 42, 287 | * 'user' => "username" 288 | * 'password' => "password123" 289 | * ) 290 | * @return $this 291 | */ 292 | public function setProxySettings(array $proxySettings) 293 | { 294 | $this->config->proxySettings = $proxySettings; 295 | 296 | return $this; 297 | } 298 | 299 | /** 300 | * Set custom curl options 301 | * 302 | * @param Array $curlOptions an array with curl options. Eg: 303 | * array( 304 | * CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4 305 | * ) 306 | * @return $this 307 | */ 308 | public function setCurlOptions(array $curlOptions) 309 | { 310 | $this->config->curlOptions = $curlOptions; 311 | 312 | return $this; 313 | } 314 | 315 | /** 316 | * Set a custom function to call before notifying Bugsnag of an error. 317 | * You can use this to call your own error handling functions, or to add 318 | * custom tabs of data to each error on your Bugsnag dashboard. 319 | * 320 | * // Adding meta-data example 321 | * function before_bugsnag_notify($error) { 322 | * $error->addMetaData(array( 323 | * "user" => array( 324 | * "name" => "James" 325 | * ) 326 | * )); 327 | * } 328 | * $bugsnag->setBeforeNotifyFunction("before_bugsnag_notify"); 329 | * @param callable $beforeNotifyFunction 330 | * @return $this 331 | */ 332 | public function setBeforeNotifyFunction($beforeNotifyFunction) 333 | { 334 | $this->config->beforeNotifyFunction = $beforeNotifyFunction; 335 | 336 | return $this; 337 | } 338 | 339 | /** 340 | * Set Bugsnag's error reporting level. 341 | * If this is not set, we'll use your current PHP error_reporting value 342 | * from your ini file or error_reporting(...) calls. 343 | * 344 | * @param Integer $errorReportingLevel the error reporting level integer 345 | * exactly as you would pass to PHP's error_reporting 346 | * @return $this 347 | */ 348 | public function setErrorReportingLevel($errorReportingLevel) 349 | { 350 | $this->config->errorReportingLevel = $errorReportingLevel; 351 | 352 | return $this; 353 | } 354 | 355 | /** 356 | * Sets whether Bugsnag should be automatically notified of unhandled 357 | * exceptions and errors. 358 | * 359 | * @param Boolean $autoNotify whether to auto notify or not 360 | * @return $this 361 | */ 362 | public function setAutoNotify($autoNotify) 363 | { 364 | $this->config->autoNotify = $autoNotify; 365 | 366 | return $this; 367 | } 368 | 369 | /** 370 | * Sets whether errors should be batched together and send at the end of 371 | * each request. 372 | * 373 | * @param Boolean $batchSending whether to batch together errors 374 | * @return $this 375 | */ 376 | public function setBatchSending($batchSending) 377 | { 378 | $this->config->batchSending = $batchSending; 379 | 380 | return $this; 381 | } 382 | 383 | /** 384 | * Sets the notifier to report as to Bugsnag. This should only be 385 | * set by other notifier libraries. 386 | * 387 | * @param Array $notifier an array of name, version, url. 388 | * @return $this 389 | */ 390 | public function setNotifier($notifier) 391 | { 392 | $this->config->notifier = $notifier; 393 | 394 | return $this; 395 | } 396 | 397 | /** 398 | * Sets whether Bugsnag should send $_ENV with each error. 399 | * 400 | * @param Boolean $sendEnvironment whether to send the environment 401 | * @return $this 402 | */ 403 | public function setSendEnvironment($sendEnvironment) 404 | { 405 | $this->config->sendEnvironment = $sendEnvironment; 406 | 407 | return $this; 408 | } 409 | 410 | /** 411 | * Sets whether Bugsnag should send $_COOKIE with each error. 412 | * 413 | * @param Boolean $sendCookies whether to send the environment 414 | * @return $this 415 | */ 416 | public function setSendCookies($sendCookies) 417 | { 418 | $this->config->sendCookies = $sendCookies; 419 | 420 | return $this; 421 | } 422 | 423 | /** 424 | * Sets whether Bugsnag should send $_SESSION with each error. 425 | * 426 | * @param Boolean $sendSession whether to send the environment 427 | * @return $this 428 | */ 429 | public function setSendSession($sendSession) 430 | { 431 | $this->config->sendSession = $sendSession; 432 | 433 | return $this; 434 | } 435 | 436 | /** 437 | * Should we send a small snippet of the code that crashed to help you 438 | * diagnose even faster from within your dashboard. 439 | * 440 | * @param Boolean $sendCode whether to send code to Bugsnag 441 | * @return $this 442 | */ 443 | public function setSendCode($sendCode) 444 | { 445 | $this->config->sendCode = $sendCode; 446 | 447 | return $this; 448 | } 449 | 450 | /** 451 | * Notify Bugsnag of a non-fatal/handled throwable 452 | * 453 | * @param Throwable $throwable the throwable to notify Bugsnag about 454 | * @param Array $metaData optional metaData to send with this error 455 | * @param String $severity optional severity of this error (fatal/error/warning/info) 456 | */ 457 | public function notifyException($throwable, array $metaData = null, $severity = null) 458 | { 459 | if (is_subclass_of($throwable, 'Throwable') || is_subclass_of($throwable, 'Exception') || get_class($throwable) == 'Exception') { 460 | $error = Bugsnag_Error::fromPHPThrowable($this->config, $this->diagnostics, $throwable); 461 | $error->setSeverity($severity); 462 | 463 | $this->notify($error, $metaData); 464 | } 465 | } 466 | 467 | /** 468 | * Notify Bugsnag of a non-fatal/handled error 469 | * 470 | * @param String $name the name of the error, a short (1 word) string 471 | * @param String $message the error message 472 | * @param Array $metaData optional metaData to send with this error 473 | * @param String $severity optional severity of this error (fatal/error/warning/info) 474 | */ 475 | public function notifyError($name, $message, array $metaData = null, $severity = null) 476 | { 477 | $error = Bugsnag_Error::fromNamedError($this->config, $this->diagnostics, $name, $message); 478 | $error->setSeverity($severity); 479 | 480 | $this->notify($error, $metaData); 481 | } 482 | 483 | // Exception handler callback, should only be called internally by PHP's set_exception_handler 484 | public function exceptionHandler($throwable) 485 | { 486 | if(!$this->config->autoNotify) { 487 | return; 488 | } 489 | 490 | $error = Bugsnag_Error::fromPHPThrowable($this->config, $this->diagnostics, $throwable); 491 | $error->setSeverity("error"); 492 | $this->notify($error); 493 | } 494 | 495 | // Exception handler callback, should only be called internally by PHP's set_error_handler 496 | public function errorHandler($errno, $errstr, $errfile = '', $errline = 0) 497 | { 498 | if(!$this->config->autoNotify || $this->config->shouldIgnoreErrorCode($errno)) { 499 | return; 500 | } 501 | 502 | $error = Bugsnag_Error::fromPHPError($this->config, $this->diagnostics, $errno, $errstr, $errfile, $errline); 503 | $this->notify($error); 504 | } 505 | 506 | // Shutdown handler callback, called when the PHP process has finished running 507 | // Should only be called internally by PHP's register_shutdown_function 508 | public function shutdownHandler() 509 | { 510 | // Get last error 511 | $lastError = error_get_last(); 512 | 513 | // Check if a fatal error caused this shutdown 514 | if (!is_null($lastError) && Bugsnag_ErrorTypes::isFatal($lastError['type']) && $this->config->autoNotify && !$this->config->shouldIgnoreErrorCode($lastError['type'])) { 515 | $error = Bugsnag_Error::fromPHPError($this->config, $this->diagnostics, $lastError['type'], $lastError['message'], $lastError['file'], $lastError['line'], true); 516 | $error->setSeverity("error"); 517 | $this->notify($error); 518 | } 519 | 520 | // Flush any buffered errors 521 | if ($this->notification) { 522 | $this->notification->deliver(); 523 | $this->notification = null; 524 | } 525 | } 526 | 527 | /** 528 | * Batches up errors into notifications for later sending 529 | * 530 | * @param Bugsnag_Error $error the error to batch up 531 | * @param array $metaData optional meta data to send with the error 532 | */ 533 | public function notify(Bugsnag_Error $error, $metaData = array()) 534 | { 535 | // Queue or send the error 536 | if ($this->sendErrorsOnShutdown()) { 537 | // Create a batch notification unless we already have one 538 | if (is_null($this->notification)) { 539 | $this->notification = new Bugsnag_Notification($this->config); 540 | } 541 | 542 | // Add this error to the notification 543 | $this->notification->addError($error, $metaData); 544 | } else { 545 | // Create and deliver notification immediately 546 | $notif = new Bugsnag_Notification($this->config); 547 | $notif->addError($error, $metaData); 548 | $notif->deliver(); 549 | } 550 | } 551 | 552 | // Should we send errors immediately or on shutdown 553 | private function sendErrorsOnShutdown() 554 | { 555 | return $this->config->batchSending && Bugsnag_Request::isRequest(); 556 | } 557 | } 558 | -------------------------------------------------------------------------------- /src/lib/bugsnag-php/Configuration.php: -------------------------------------------------------------------------------- 1 | 'Bugsnag PHP (Official)', 21 | 'version' => '2.6.1', 22 | 'url' => 'https://bugsnag.com', 23 | ); 24 | public $sendEnvironment = false; 25 | public $sendCookies = true; 26 | public $sendSession = true; 27 | public $sendCode = true; 28 | public $stripPath; 29 | public $stripPathRegex; 30 | 31 | public $context; 32 | public $type; 33 | public $user; 34 | public $releaseStage = 'production'; 35 | public $appVersion; 36 | public $hostname; 37 | 38 | public $metaData; 39 | public $beforeNotifyFunction; 40 | public $errorReportingLevel; 41 | 42 | public $curlOptions = array(); 43 | 44 | public $debug = false; 45 | 46 | public function __construct() 47 | { 48 | $this->timeout = Bugsnag_Configuration::$DEFAULT_TIMEOUT; 49 | } 50 | 51 | public function getNotifyEndpoint() 52 | { 53 | if (is_null($this->endpoint)) { 54 | return $this->useSSL ? Bugsnag_Configuration::$DEFAULT_ENDPOINT : Bugsnag_Configuration::$DEFAULT_NON_SSL_ENDPOINT; 55 | } elseif (preg_match('/^(http:\/\/|https:\/\/)/', $this->endpoint)) { 56 | return $this->endpoint; 57 | } else { 58 | return ($this->useSSL ? "https" : "http")."://".$this->endpoint; 59 | } 60 | } 61 | 62 | public function shouldNotify() 63 | { 64 | return is_null($this->notifyReleaseStages) || (is_array($this->notifyReleaseStages) && in_array($this->releaseStage, $this->notifyReleaseStages)); 65 | } 66 | 67 | public function shouldIgnoreErrorCode($code) 68 | { 69 | if (isset($this->errorReportingLevel)) { 70 | return !($this->errorReportingLevel & $code); 71 | } else { 72 | return !(error_reporting() & $code); 73 | } 74 | } 75 | 76 | public function setProjectRoot($projectRoot) 77 | { 78 | $this->projectRoot = $projectRoot; 79 | $this->projectRootRegex = '/'.preg_quote($projectRoot, '/')."[\\/]?/i"; 80 | if (is_null($this->stripPath)) { 81 | $this->setStripPath($projectRoot); 82 | } 83 | } 84 | 85 | public function setStripPath($stripPath) 86 | { 87 | $this->stripPath = $stripPath; 88 | $this->stripPathRegex = '/'.preg_quote($stripPath, '/')."[\\/]?/i"; 89 | } 90 | 91 | public function get($prop, $default = null) 92 | { 93 | $configured = $this->$prop; 94 | 95 | if (is_array($configured) && is_array($default)) { 96 | return array_merge($default, $configured); 97 | } else { 98 | return $configured ? $configured : $default; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/lib/bugsnag-php/Diagnostics.php: -------------------------------------------------------------------------------- 1 | config = $config; 10 | } 11 | 12 | public function getAppData() 13 | { 14 | $appData = array(); 15 | 16 | if (!is_null($this->config->appVersion)) { 17 | $appData['version'] = $this->config->appVersion; 18 | } 19 | 20 | if (!is_null($this->config->releaseStage)) { 21 | $appData['releaseStage'] = $this->config->releaseStage; 22 | } 23 | 24 | if (!is_null($this->config->type)) { 25 | $appData['type'] = $this->config->type; 26 | } 27 | 28 | return $appData; 29 | } 30 | 31 | public function getDeviceData() 32 | { 33 | return array( 34 | 'hostname' => $this->config->get('hostname', php_uname('n')), 35 | ); 36 | } 37 | 38 | public function getContext() 39 | { 40 | return $this->config->get('context', Bugsnag_Request::getContext()); 41 | } 42 | 43 | public function getUser() 44 | { 45 | $defaultUser = array(); 46 | $userId = Bugsnag_Request::getUserId(); 47 | 48 | if (!is_null($userId)) { 49 | $defaultUser['id'] = $userId; 50 | } 51 | 52 | return $this->config->get('user', $defaultUser); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/lib/bugsnag-php/Error.php: -------------------------------------------------------------------------------- 1 | setPHPError($code, $message, $file, $line, $fatal); 29 | 30 | return $error; 31 | } 32 | 33 | public static function fromPHPThrowable(Bugsnag_Configuration $config, Bugsnag_Diagnostics $diagnostics, $throwable) 34 | { 35 | $error = new Bugsnag_Error($config, $diagnostics); 36 | $error->setPHPException($throwable); 37 | 38 | return $error; 39 | } 40 | 41 | public static function fromNamedError(Bugsnag_Configuration $config, Bugsnag_Diagnostics $diagnostics, $name, $message = null) 42 | { 43 | $error = new Bugsnag_Error($config, $diagnostics); 44 | $error->setName($name) 45 | ->setMessage($message) 46 | ->setStacktrace(Bugsnag_Stacktrace::generate($config)); 47 | 48 | return $error; 49 | } 50 | 51 | // Private constructor (for use only by the static methods above) 52 | private function __construct(Bugsnag_Configuration $config, Bugsnag_Diagnostics $diagnostics) 53 | { 54 | $this->config = $config; 55 | $this->diagnostics = $diagnostics; 56 | } 57 | 58 | public function setName($name) 59 | { 60 | $this->name = $name; 61 | 62 | return $this; 63 | } 64 | 65 | public function setMessage($message) 66 | { 67 | $this->message = $message; 68 | 69 | return $this; 70 | } 71 | 72 | public function setGroupingHash($groupingHash) 73 | { 74 | $this->groupingHash = $groupingHash; 75 | 76 | return $this; 77 | } 78 | 79 | public function setStacktrace(Bugsnag_Stacktrace $stacktrace) 80 | { 81 | $this->stacktrace = $stacktrace; 82 | 83 | return $this; 84 | } 85 | 86 | public function setSeverity($severity) 87 | { 88 | if (!is_null($severity)) { 89 | if (in_array($severity, Bugsnag_Error::$VALID_SEVERITIES)) { 90 | $this->severity = $severity; 91 | } else { 92 | error_log('Bugsnag Warning: Tried to set error severity to '.$severity.' which is not allowed.'); 93 | } 94 | } 95 | 96 | return $this; 97 | } 98 | 99 | public function setPHPException($exception) 100 | { 101 | if (version_compare(PHP_VERSION, '7.0.0', '>=')) { 102 | if (!$exception instanceof \Throwable) { 103 | error_log('Bugsnag Warning: Exception must implement interface \Throwable.'); 104 | return; 105 | } 106 | } else { 107 | if (!$exception instanceof \Exception) { 108 | error_log('Bugsnag Warning: Exception must be instance of \Exception.'); 109 | return; 110 | } 111 | } 112 | 113 | $this->setName(get_class($exception)) 114 | ->setMessage($exception->getMessage()) 115 | ->setStacktrace(Bugsnag_Stacktrace::fromBacktrace($this->config, $exception->getTrace(), $exception->getFile(), $exception->getLine())); 116 | 117 | if (method_exists($exception, 'getPrevious')) { 118 | $this->setPrevious($exception->getPrevious()); 119 | } 120 | 121 | return $this; 122 | } 123 | 124 | public function setPHPError($code, $message, $file, $line, $fatal = false) 125 | { 126 | if ($fatal) { 127 | // Generating stacktrace for PHP fatal errors is not possible, 128 | // since this code executes when the PHP process shuts down, 129 | // rather than at the time of the crash. 130 | // 131 | // In these situations, we generate a "stacktrace" containing only 132 | // the line and file number where the crash occurred. 133 | $stacktrace = Bugsnag_Stacktrace::fromFrame($this->config, $file, $line); 134 | } else { 135 | $stacktrace = Bugsnag_Stacktrace::generate($this->config); 136 | } 137 | 138 | $this->setName(Bugsnag_ErrorTypes::getName($code)) 139 | ->setMessage($message) 140 | ->setSeverity(Bugsnag_ErrorTypes::getSeverity($code)) 141 | ->setStacktrace($stacktrace); 142 | 143 | return $this; 144 | } 145 | 146 | public function setMetaData($metaData) 147 | { 148 | if (is_array($metaData)) { 149 | $this->metaData = array_merge_recursive($this->metaData, $metaData); 150 | } 151 | 152 | return $this; 153 | } 154 | 155 | public function setPrevious($exception) 156 | { 157 | if ($exception) { 158 | $this->previous = Bugsnag_Error::fromPHPThrowable($this->config, $this->diagnostics, $exception); 159 | } 160 | 161 | return $this; 162 | } 163 | 164 | public function toArray() 165 | { 166 | $errorArray = array( 167 | 'app' => $this->diagnostics->getAppData(), 168 | 'device' => $this->diagnostics->getDeviceData(), 169 | 'user' => $this->diagnostics->getUser(), 170 | 'context' => $this->diagnostics->getContext(), 171 | 'payloadVersion' => $this->payloadVersion, 172 | 'severity' => $this->severity, 173 | 'exceptions' => $this->exceptionArray(), 174 | 'metaData' => $this->cleanupObj($this->metaData), 175 | ); 176 | 177 | if (isset($this->groupingHash)) { 178 | $errorArray['groupingHash'] = $this->groupingHash; 179 | } 180 | 181 | return $errorArray; 182 | } 183 | 184 | public function exceptionArray() 185 | { 186 | if ($this->previous) { 187 | $exceptionArray = $this->previous->exceptionArray(); 188 | } else { 189 | $exceptionArray = array(); 190 | } 191 | 192 | $exceptionArray[] = array( 193 | 'errorClass' => $this->name, 194 | 'message' => $this->message, 195 | 'stacktrace' => $this->stacktrace->toArray(), 196 | ); 197 | 198 | return $this->cleanupObj($exceptionArray); 199 | } 200 | 201 | private function cleanupObj($obj) 202 | { 203 | if (is_null($obj)) { 204 | return null; 205 | } 206 | 207 | if (is_array($obj)) { 208 | $cleanArray = array(); 209 | foreach ($obj as $key => $value) { 210 | // Apply filters if required 211 | if (is_array($this->config->filters)) { 212 | // Check if this key should be filtered 213 | $shouldFilter = false; 214 | foreach ($this->config->filters as $filter) { 215 | if (strpos($key, $filter) !== false) { 216 | $shouldFilter = true; 217 | break; 218 | } 219 | } 220 | 221 | // Apply filters 222 | if ($shouldFilter) { 223 | $cleanArray[$key] = '[FILTERED]'; 224 | } else { 225 | $cleanArray[$key] = $this->cleanupObj($value); 226 | } 227 | } 228 | } 229 | 230 | return $cleanArray; 231 | } elseif (is_string($obj)) { 232 | // UTF8-encode if not already encoded 233 | if (function_exists('mb_detect_encoding') && !mb_detect_encoding($obj, 'UTF-8', true)) { 234 | return utf8_encode($obj); 235 | } else { 236 | return $obj; 237 | } 238 | } elseif (is_object($obj)) { 239 | // json_encode -> json_decode trick turns an object into an array 240 | return $this->cleanupObj(json_decode(json_encode($obj), true)); 241 | } else { 242 | return $obj; 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/lib/bugsnag-php/ErrorTypes.php: -------------------------------------------------------------------------------- 1 | array( 7 | 'name' => 'PHP Fatal Error', 8 | 'severity' => 'error', 9 | ), 10 | 11 | E_WARNING => array( 12 | 'name' => 'PHP Warning', 13 | 'severity' => 'warning', 14 | ), 15 | 16 | E_PARSE => array( 17 | 'name' => 'PHP Parse Error', 18 | 'severity' => 'error', 19 | ), 20 | 21 | E_NOTICE => array( 22 | 'name' => 'PHP Notice', 23 | 'severity' => 'info', 24 | ), 25 | 26 | E_CORE_ERROR => array( 27 | 'name' => 'PHP Core Error', 28 | 'severity' => 'error', 29 | ), 30 | 31 | E_CORE_WARNING => array( 32 | 'name' => 'PHP Core Warning', 33 | 'severity' => 'warning', 34 | ), 35 | 36 | E_COMPILE_ERROR => array( 37 | 'name' => 'PHP Compile Error', 38 | 'severity' => 'error', 39 | ), 40 | 41 | E_COMPILE_WARNING => array( 42 | 'name' => 'PHP Compile Warning', 43 | 'severity' => 'warning', 44 | ), 45 | 46 | E_USER_ERROR => array( 47 | 'name' => 'User Error', 48 | 'severity' => 'error', 49 | ), 50 | 51 | E_USER_WARNING => array( 52 | 'name' => 'User Warning', 53 | 'severity' => 'warning', 54 | ), 55 | 56 | E_USER_NOTICE => array( 57 | 'name' => 'User Notice', 58 | 'severity' => 'info', 59 | ), 60 | 61 | E_STRICT => array( 62 | 'name' => 'PHP Strict', 63 | 'severity' => 'info', 64 | ), 65 | 66 | E_RECOVERABLE_ERROR => array( 67 | 'name' => 'PHP Recoverable Error', 68 | 'severity' => 'error', 69 | ), 70 | 71 | // E_DEPRECATED (Since PHP 5.3.0) 72 | 8192 => array( 73 | 'name' => 'PHP Deprecated', 74 | 'severity' => 'info', 75 | ), 76 | 77 | // E_USER_DEPRECATED (Since PHP 5.3.0) 78 | 16384 => array( 79 | 'name' => 'User Deprecated', 80 | 'severity' => 'info', 81 | ), 82 | ); 83 | 84 | public static function isFatal($code) 85 | { 86 | return self::getSeverity($code) == 'error'; 87 | } 88 | 89 | public static function getName($code) 90 | { 91 | if (array_key_exists($code, self::$ERROR_TYPES)) { 92 | return self::$ERROR_TYPES[$code]['name']; 93 | } else { 94 | return "Unknown"; 95 | } 96 | } 97 | 98 | public static function getSeverity($code) 99 | { 100 | if (array_key_exists($code, self::$ERROR_TYPES)) { 101 | return self::$ERROR_TYPES[$code]['severity']; 102 | } else { 103 | return "error"; 104 | } 105 | } 106 | 107 | public static function getLevelsForSeverity($severity) 108 | { 109 | $levels = 0; 110 | foreach (Bugsnag_ErrorTypes::$ERROR_TYPES as $level => $info) { 111 | if ($info['severity'] == $severity) { 112 | $levels |= $level; 113 | } 114 | } 115 | 116 | return $levels; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/lib/bugsnag-php/Notification.php: -------------------------------------------------------------------------------- 1 | config = $config; 14 | } 15 | 16 | public function addError(Bugsnag_Error $error, $passedMetaData = array()) 17 | { 18 | // Check if this error should be sent to Bugsnag 19 | if (!$this->config->shouldNotify()) { 20 | return false; 21 | } 22 | 23 | // Add global meta-data to error 24 | $error->setMetaData($this->config->metaData); 25 | 26 | // Add request meta-data to error 27 | if (Bugsnag_Request::isRequest()) { 28 | $error->setMetaData(Bugsnag_Request::getRequestMetaData()); 29 | } 30 | 31 | // Session Tab 32 | if ($this->config->sendSession && !empty($_SESSION)) { 33 | $error->setMetaData(array('session' => $_SESSION)); 34 | } 35 | 36 | // Cookies Tab 37 | if ($this->config->sendCookies && !empty($_COOKIE)) { 38 | $error->setMetaData(array('cookies' => $_COOKIE)); 39 | } 40 | 41 | // Add environment meta-data to error 42 | if ($this->config->sendEnvironment && !empty($_ENV)) { 43 | $error->setMetaData(array("Environment" => $_ENV)); 44 | } 45 | 46 | // Add user-specified meta-data to error 47 | $error->setMetaData($passedMetaData); 48 | 49 | // Run beforeNotify function (can cause more meta-data to be merged) 50 | if (isset($this->config->beforeNotifyFunction) && is_callable($this->config->beforeNotifyFunction)) { 51 | $beforeNotifyReturn = call_user_func($this->config->beforeNotifyFunction, $error); 52 | } 53 | 54 | // Skip this error if the beforeNotify function returned FALSE 55 | if (!isset($beforeNotifyReturn) || $beforeNotifyReturn !== false) { 56 | $this->errorQueue[] = $error; 57 | 58 | return true; 59 | } else { 60 | return false; 61 | } 62 | } 63 | 64 | public function toArray() 65 | { 66 | $events = array(); 67 | foreach ($this->errorQueue as $error) { 68 | $errorArray = $error->toArray(); 69 | 70 | if (!is_null($errorArray)) { 71 | $events[] = $errorArray; 72 | } 73 | } 74 | 75 | return array( 76 | 'apiKey' => $this->config->apiKey, 77 | 'notifier' => $this->config->notifier, 78 | 'events' => $events, 79 | ); 80 | } 81 | 82 | public function deliver() 83 | { 84 | if (!empty($this->errorQueue)) { 85 | // Post the request to bugsnag 86 | $this->postJSON($this->config->getNotifyEndpoint(), $this->toArray()); 87 | 88 | // Clear the error queue 89 | $this->errorQueue = array(); 90 | } 91 | } 92 | 93 | public function postJSON($url, $data) 94 | { 95 | $body = json_encode($data); 96 | 97 | // Prefer cURL if it is installed, otherwise fall back to fopen() 98 | // cURL supports both timeouts and proxies 99 | if (function_exists('curl_version')) { 100 | $this->postWithCurl($url, $body); 101 | } elseif (ini_get('allow_url_fopen')) { 102 | $this->postWithFopen($url, $body); 103 | } else { 104 | error_log('Bugsnag Warning: Couldn\'t notify (neither cURL or allow_url_fopen are available on your PHP installation)'); 105 | } 106 | } 107 | 108 | private function postWithCurl($url, $body) 109 | { 110 | $http = curl_init($url); 111 | 112 | // Default curl settings 113 | curl_setopt($http, CURLOPT_HEADER, false); 114 | curl_setopt($http, CURLOPT_RETURNTRANSFER, true); 115 | curl_setopt($http, CURLOPT_POST, true); 116 | curl_setopt($http, CURLOPT_HTTPHEADER, array(Bugsnag_Notification::$CONTENT_TYPE_HEADER)); 117 | curl_setopt($http, CURLOPT_POSTFIELDS, $body); 118 | curl_setopt($http, CURLOPT_CONNECTTIMEOUT, $this->config->timeout); 119 | curl_setopt($http, CURLOPT_SSL_VERIFYPEER, false); 120 | curl_setopt($http, CURLOPT_VERBOSE, false); 121 | if (defined('HHVM_VERSION')) { 122 | curl_setopt($http, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); 123 | } else { 124 | curl_setopt($http, CURL_IPRESOLVE_V4, true); 125 | } 126 | 127 | if (!empty($this->config->curlOptions)) { 128 | foreach ($this->config->curlOptions as $option => $value) { 129 | curl_setopt($http, $option, $value); 130 | } 131 | } 132 | // Apply proxy settings (if present) 133 | if (count($this->config->proxySettings)) { 134 | if (isset($this->config->proxySettings['host'])) { 135 | curl_setopt($http, CURLOPT_PROXY, $this->config->proxySettings['host']); 136 | } 137 | if (isset($this->config->proxySettings['port'])) { 138 | curl_setopt($http, CURLOPT_PROXYPORT, $this->config->proxySettings['port']); 139 | } 140 | if (isset($this->config->proxySettings['user'])) { 141 | $userPassword = $this->config->proxySettings['user'].':'; 142 | $userPassword .= isset($this->config->proxySettings['password']) ? $this->config->proxySettings['password'] : ''; 143 | curl_setopt($http, CURLOPT_PROXYUSERPWD, $userPassword); 144 | } 145 | } 146 | 147 | // Execute the request and fetch the response 148 | $responseBody = curl_exec($http); 149 | $statusCode = curl_getinfo($http, CURLINFO_HTTP_CODE); 150 | 151 | if ($statusCode > 200) { 152 | error_log('Bugsnag Warning: Couldn\'t notify ('.$responseBody.')'); 153 | 154 | if($this->config->debug) { 155 | error_log('Bugsnag Debug: Attempted to post to URL - "'.$url.'"'); 156 | error_log('Bugsnag Debug: Attempted to post payload - "'.$body.'"'); 157 | } 158 | } 159 | 160 | if (curl_errno($http)) { 161 | error_log('Bugsnag Warning: Couldn\'t notify ('.curl_error($http).')'); 162 | } 163 | 164 | curl_close($http); 165 | } 166 | 167 | private function postWithFopen($url, $body) 168 | { 169 | // Warn about lack of proxy support if we are using fopen() 170 | if (count($this->config->proxySettings)) { 171 | error_log('Bugsnag Warning: Can\'t use proxy settings unless cURL is installed'); 172 | } 173 | 174 | // Create the request context 175 | $context = stream_context_create(array( 176 | 'http' => array( 177 | 'method' => 'POST', 178 | 'header' => Bugsnag_Notification::$CONTENT_TYPE_HEADER.'\r\n', 179 | 'content' => $body, 180 | 'timeout' => $this->config->timeout 181 | ), 182 | 'ssl' => array( 183 | 'verify_peer' => false, 184 | ), 185 | )); 186 | 187 | // Execute the request and fetch the response 188 | if ($stream = fopen($url, 'rb', false, $context)) { 189 | $response = stream_get_contents($stream); 190 | 191 | if (!$response) { 192 | error_log('Bugsnag Warning: Couldn\'t notify (no response)'); 193 | } 194 | } else { 195 | error_log('Bugsnag Warning: Couldn\'t notify (fopen failed)'); 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/lib/bugsnag-php/Request.php: -------------------------------------------------------------------------------- 1 | $value) { 81 | if (substr($name, 0, 5) == 'HTTP_') { 82 | $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; 83 | } 84 | } 85 | 86 | return $headers; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/lib/bugsnag-php/Stacktrace.php: -------------------------------------------------------------------------------- 1 | = 0) { 15 | $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS & ~DEBUG_BACKTRACE_PROVIDE_OBJECT); 16 | } elseif (version_compare(PHP_VERSION, '5.2.5') >= 0) { 17 | $backtrace = debug_backtrace(false); 18 | } else { 19 | $backtrace = debug_backtrace(); 20 | } 21 | 22 | return self::fromBacktrace($config, $backtrace, "[generator]", 0); 23 | } 24 | 25 | public static function fromFrame($config, $file, $line) 26 | { 27 | $stacktrace = new Bugsnag_Stacktrace($config); 28 | $stacktrace->addFrame($file, $line, "[unknown]"); 29 | 30 | return $stacktrace; 31 | } 32 | 33 | public static function fromBacktrace($config, $backtrace, $topFile, $topLine) 34 | { 35 | $stacktrace = new Bugsnag_Stacktrace($config); 36 | 37 | // PHP backtrace's are misaligned, we need to shift the file/line down a frame 38 | foreach ($backtrace as $frame) { 39 | if (!self::frameInsideBugsnag($frame)) { 40 | $stacktrace->addFrame( 41 | $topFile, 42 | $topLine, 43 | isset($frame['function']) ? $frame['function'] : null, 44 | isset($frame['class']) ? $frame['class'] : null 45 | ); 46 | } 47 | 48 | if (isset($frame['file']) && isset($frame['line'])) { 49 | $topFile = $frame['file']; 50 | $topLine = $frame['line']; 51 | } else { 52 | $topFile = "[internal]"; 53 | $topLine = 0; 54 | } 55 | } 56 | 57 | // Add a final stackframe for the "main" method 58 | $stacktrace->addFrame($topFile, $topLine, '[main]'); 59 | 60 | return $stacktrace; 61 | } 62 | 63 | public static function frameInsideBugsnag($frame) 64 | { 65 | return isset($frame['class']) && strpos($frame['class'], 'Bugsnag_') === 0; 66 | } 67 | 68 | public function __construct($config) 69 | { 70 | $this->config = $config; 71 | } 72 | 73 | public function toArray() 74 | { 75 | return $this->frames; 76 | } 77 | 78 | public function addFrame($file, $line, $method, $class = null) 79 | { 80 | // Account for special "filenames" in eval'd code 81 | $matches = array(); 82 | if (preg_match("/^(.*?)\((\d+)\) : (?:eval\(\)'d code|runtime-created function)$/", $file, $matches)) { 83 | $file = $matches[1]; 84 | $line = $matches[2]; 85 | } 86 | 87 | // Construct the frame 88 | $frame = array( 89 | 'lineNumber' => $line, 90 | 'method' => $class ? "$class::$method" : $method, 91 | ); 92 | 93 | // Attach some lines of code for context 94 | if($this->config->sendCode) { 95 | $frame['code'] = $this->getCode($file, $line, Bugsnag_Stacktrace::$DEFAULT_NUM_LINES); 96 | } 97 | 98 | // Check if this frame is inProject 99 | $frame['inProject'] = !is_null($this->config->projectRootRegex) && preg_match($this->config->projectRootRegex, $file); 100 | 101 | // Strip out projectRoot from start of file path 102 | if (is_null($this->config->stripPathRegex)) { 103 | $frame['file'] = $file; 104 | } else { 105 | $frame['file'] = preg_replace($this->config->stripPathRegex, '', $file); 106 | } 107 | 108 | $this->frames[] = $frame; 109 | } 110 | 111 | private function getCode($path, $line, $numLines) 112 | { 113 | if (empty($path) || empty($line) || !file_exists($path)) { 114 | return NULL; 115 | } 116 | 117 | try { 118 | // Get the number of lines in the file 119 | $file = new SplFileObject($path); 120 | $file->seek(PHP_INT_MAX); 121 | $totalLines = $file->key() + 1; 122 | 123 | // Work out which lines we should fetch 124 | $start = max($line - floor($numLines / 2), 1); 125 | $end = $start + ($numLines - 1); 126 | if ($end > $totalLines) { 127 | $end = $totalLines; 128 | $start = max($end - ($numLines - 1), 1); 129 | } 130 | 131 | // Get the code for this range 132 | $code = array(); 133 | 134 | $file->seek($start - 1); 135 | while ($file->key() < $end) { 136 | $code[$file->key() + 1] = rtrim(substr($file->current(), 0, Bugsnag_Stacktrace::$MAX_LINE_LENGTH)); 137 | $file->next(); 138 | } 139 | 140 | return $code; 141 | } catch (RuntimeException $ex) { 142 | return null; 143 | } 144 | } 145 | } 146 | --------------------------------------------------------------------------------