From 0c48c5769362c86676a43285d4812c41b4253a04 Mon Sep 17 00:00:00 2001 From: Lucas van Staden Date: Fri, 18 Mar 2016 12:33:45 +0800 Subject: [PATCH 1/6] Integrate Airbrake --- .../FireGento/Logger/Model/Airbrake.php | 181 +++++++++++++ .../community/FireGento/Logger/etc/config.xml | 8 + .../community/FireGento/Logger/etc/system.xml | 60 +++++ src/lib/Airbrake/Client.php | 119 +++++++++ src/lib/Airbrake/Configuration.php | 106 ++++++++ src/lib/Airbrake/Connection.php | 67 +++++ src/lib/Airbrake/EventHandler.php | 182 +++++++++++++ src/lib/Airbrake/Exception.php | 15 ++ src/lib/Airbrake/Notice.php | 109 ++++++++ src/lib/Airbrake/Record.php | 241 ++++++++++++++++++ src/lib/Airbrake/Resque/NotifyJob.php | 18 ++ src/lib/Airbrake/Version.php | 18 ++ 12 files changed, 1124 insertions(+) create mode 100644 src/app/code/community/FireGento/Logger/Model/Airbrake.php create mode 100644 src/lib/Airbrake/Client.php create mode 100644 src/lib/Airbrake/Configuration.php create mode 100644 src/lib/Airbrake/Connection.php create mode 100644 src/lib/Airbrake/EventHandler.php create mode 100644 src/lib/Airbrake/Exception.php create mode 100644 src/lib/Airbrake/Notice.php create mode 100644 src/lib/Airbrake/Record.php create mode 100644 src/lib/Airbrake/Resque/NotifyJob.php create mode 100644 src/lib/Airbrake/Version.php diff --git a/src/app/code/community/FireGento/Logger/Model/Airbrake.php b/src/app/code/community/FireGento/Logger/Model/Airbrake.php new file mode 100644 index 00000000..cdea1470 --- /dev/null +++ b/src/app/code/community/FireGento/Logger/Model/Airbrake.php @@ -0,0 +1,181 @@ + + * @copyright 2013 FireGento Team (http://www.firegento.com) + * @license http://opensource.org/licenses/gpl-3.0 GNU General Public License, version 3 (GPLv3) + */ + + +/** + * Class FireGento_Logger_Model_Airbrake + * + * This file was ported from Elgentos_CodebaseExceptions_Helper_Data + * into this logger module. + * + * https://github.com/airbrake/Airbrake-Magento/blob/master/app/code/community/Elgentos/CodebaseExceptions/Helper/Data.php + * + */ + + +require_once Mage::getBaseDir('lib') . DS . 'Airbrake' . DS . 'Client.php'; +require_once Mage::getBaseDir('lib') . DS . 'Airbrake' . DS . 'Configuration.php'; + +class FireGento_Logger_Model_Airbrake extends Zend_Log_Writer_Abstract +{ + + public function __construct() + { + $apiKey = Mage::getStoreConfig('logger/airbrake/apikey'); + + if ($this->isDisabled()) { + return; + } + + + $options = array(); + // REQUEST_URI is not available in the CLI context + if (isset($_SERVER['REQUEST_URI'])) { + $requestUri = explode("/", $_SERVER['REQUEST_URI']); + $options['action'] = array_pop($requestUri); + $options['component'] = implode('/', array_slice($requestUri, -2)); + } else { + $options['action'] = $_SERVER['PHP_SELF']; + $options['component'] = $_SERVER['PHP_SELF']; + } + + $projectRoot = explode('/', $_SERVER['PHP_SELF']); + array_pop($projectRoot); + $options['projectRoot'] = implode('/', $projectRoot) . '/'; + $options['host'] = Mage::getStoreConfig('logger/airbrake/host'); + $options['secure'] = Mage::getStoreConfig('logger/airbrake/secure'); + $options['environmentName'] = Mage::getStoreConfig('logger/airbrake/environment'); + $options['timeout'] = Mage::getStoreConfig('logger/airbrake/timeout'); + $config = new Airbrake\Configuration($apiKey, $options); + $this->client = new Airbrake\Client($config); + } + + + /** + * Write a message to the log. + * + * @param array $event event data + * @return void + * @throws Zend_Log_Exception + */ + protected function _write($event) + { + $this->sendToAirbrake($event['message'], 4); + } + + protected function isDisabled() + { + $apiKey = Mage::getStoreConfig('logger/airbrake/apikey'); + if (strlen(trim($apiKey)) == 0) { + return true; + } + return false; + } + + public function insertException($reportData) + { + if ($this->isDisabled()) { + return; + } + $backtraceLines = explode("\n", $reportData[1]); + $backtraces = $this->formatStackTraceArray($backtraceLines); + + $this->client->notifyOnError($reportData[0], $backtraces); + } + + /** + * @param string $message + * @param int $backtraceLinesToSkip Number of backtrace lines/frames to skip + */ + public function sendToAirbrake($message, $backtraceLinesToSkip = 1) + { + if ($this->isDisabled()) { + return; + } + + $message = trim($message); + $messageArray = explode("\n", $message); + if (empty($messageArray)) { + return; + } + $errorClass = 'PHP Error'; + $errorMessage = array_shift($messageArray); + $backTrace = array_slice(debug_backtrace(), $backtraceLinesToSkip); + + $matches = array(); + if (preg_match('/exception \'(.*)\' with message \'(.*)\' in .*/', $errorMessage, $matches)) { + $errorMessage = $matches[2]; + $errorClass = $matches[1]; + } + if (count($messageArray) > 0) { + $errorMessage .= '... [truncated]'; + } + + $notice = new \Airbrake\Notice; + $notice->load( + array( + 'errorClass' => $errorClass, + 'backtrace' => $backTrace, + 'errorMessage' => $errorMessage, + ) + ); + + $this->client->notify($notice); + } + + /** + * @param array $backtraceLines + * @return array + */ + protected function formatStackTraceArray($backtraceLines) + { + $backtraces = array(); + + foreach ($backtraceLines as $backtrace) { + $temp = array(); + $parts = explode(': ', $backtrace); + + if (isset($parts[1])) { + $temp['function'] = $parts[1]; + } + + $temp['file'] = substr($parts[0], 0, stripos($parts[0], '(')); + $temp['line'] = substr( + $parts[0], stripos($parts[0], '(') + 1, (stripos($parts[0], ')') - 1) - stripos($parts[0], '(') + ); + + if (!empty($temp['function'])) { + $backtraces[] = $temp; + } + } + return $backtraces; + } + + /** + * Satisfy newer Zend Framework + * + * @param array|Zend_Config $config Configuration + * + * @return void|Zend_Log_FactoryInterface + */ + public static function factory($config) + { + + } +} diff --git a/src/app/code/community/FireGento/Logger/etc/config.xml b/src/app/code/community/FireGento/Logger/etc/config.xml index 787d334d..90b306af 100644 --- a/src/app/code/community/FireGento/Logger/etc/config.xml +++ b/src/app/code/community/FireGento/Logger/etc/config.xml @@ -158,6 +158,10 @@ FireGento_Logger_Model_Redis + + + FireGento_Logger_Model_Airbrake + @@ -172,6 +176,7 @@ + advanced @@ -264,6 +269,9 @@ confirmation]]> magento-log + + 60 + diff --git a/src/app/code/community/FireGento/Logger/etc/system.xml b/src/app/code/community/FireGento/Logger/etc/system.xml index 2e8b29ac..3017394e 100644 --- a/src/app/code/community/FireGento/Logger/etc/system.xml +++ b/src/app/code/community/FireGento/Logger/etc/system.xml @@ -707,6 +707,66 @@ + + + 1 + 600 + 1 + 1 + 1 + Enable Report Exception Logging to complete the Airbrake setup.]]> + + + + select + firegento_logger/system_config_source_prioritydefault + 11 + 1 + Choose the lowest priority level to be logged. + + + + text + 20 + 1 + 0 + 0 + + + + text + 30 + 1 + 0 + 0 + + + + select + adminhtml/system_config_source_yesno + 40 + 1 + 0 + 0 + + + + text + 50 + 1 + 0 + 0 + + + + text + 60 + 1 + 0 + 0 + + + diff --git a/src/lib/Airbrake/Client.php b/src/lib/Airbrake/Client.php new file mode 100644 index 00000000..f1634382 --- /dev/null +++ b/src/lib/Airbrake/Client.php @@ -0,0 +1,119 @@ + + * @copyright (c) 2011-2013 Drew Butler + * @license http://www.opensource.org/licenses/mit-license.php + */ +class Client +{ + protected $configuration = null; + protected $connection = null; + protected $notice = null; + + /** + * Build the Client with the Airbrake Configuration. + * + * @throws Airbrake\Exception + * @param Configuration $configuration + */ + public function __construct(Configuration $configuration) + { + $configuration->verify(); + + $this->configuration = $configuration; + $this->connection = new Connection($configuration); + } + + /** + * Notify on an error message. + * + * @param string $message + * @param array $backtrace + * @return string + */ + public function notifyOnError($message, array $backtrace = null) + { + if (!$backtrace) { + $backtrace = debug_backtrace(); + if (count($backtrace) > 1) { + array_shift($backtrace); + } + } + + $notice = new Notice; + $notice->load(array( + 'errorClass' => 'PHP Error', + 'backtrace' => $backtrace, + 'errorMessage' => $message, + )); + + return $this->notify($notice); + } + + /** + * Notify on an exception + * + * @param Airbrake\Notice $notice + * @return string + */ + public function notifyOnException(Exception $exception) + { + $notice = new Notice; + $notice->load(array( + 'errorClass' => get_class($exception), + 'backtrace' => $this->cleanBacktrace($exception->getTrace() ?: debug_backtrace()), + 'errorMessage' => $exception->getMessage().' in '.$exception->getFile().' on line '.$exception->getLine(), + )); + + return $this->notify($notice); + } + + /** + * Notify about the notice. + * + * If there is a PHP Resque client given in the configuration, then use that to queue up a job to + * send this out later. This should help speed up operations. + * + * @param Airbrake\Notice $notice + */ + public function notify(Notice $notice) + { + if ($this->configuration->queue && class_exists('Resque')) { + $data = array('notice' => serialize($notice), 'configuration' => serialize($this->configuration)); + \Resque::enqueue($this->configuration->queue, 'Airbrake\\Resque\\NotifyJob', $data); + return; + } + + return $this->connection->send($notice); + } + + /** + * Clean the backtrace of unneeded junk. + * + * @param array $backtrace + * @return array + */ + protected function cleanBacktrace($backtrace) + { + foreach ($backtrace as &$item) { + unset($item['args']); + } + + return $backtrace; + } +} \ No newline at end of file diff --git a/src/lib/Airbrake/Configuration.php b/src/lib/Airbrake/Configuration.php new file mode 100644 index 00000000..378ea48a --- /dev/null +++ b/src/lib/Airbrake/Configuration.php @@ -0,0 +1,106 @@ + + * @copyright (c) 2011-2013 Drew Butler + * @license http://www.opensource.org/licenses/mit-license.php + */ +class Configuration extends Record +{ + protected $_apiKey; + protected $_timeout = 2; + protected $_environmentName = 'production'; + protected $_serverData; + protected $_getData; + protected $_postData; + protected $_sessionData; + protected $_component; + protected $_action; + protected $_projectRoot; + protected $_url; + protected $_hostname; + protected $_queue; + protected $_secure = false; + protected $_host = 'api.airbrake.io'; + protected $_resource = '/notifier_api/v2/notices'; + protected $_apiEndPoint; + + /** + * Load the given data array to the record. + * + * @param string $apiKey + * @param array|stdClass $data + */ + public function __construct($apiKey, $data = array()) + { + $data['apiKey'] = $apiKey; + parent::__construct($data); + } + + /** + * Initialize the data source. + */ + protected function initialize() + { + if (!$this->serverData) { + $this->serverData = (array) $_SERVER; + } + + if (!$this->getData) { + $this->getData = (array) $_GET; + } + + if (!$this->postData) { + $this->postData = (array) $_POST; + } + + if (!$this->sessionData && isset($_SESSION)) { + $this->sessionData = (array) $_SESSION; + } + + if (!$this->projectRoot) { + $this->projectRoot = isset($this->serverData['_']) ? $this->serverData['_'] : $this->serverData['DOCUMENT_ROOT']; + } + + if (!$this->url) { + $this->url = isset($this->serverData['REDIRECT_URL']) ? $this->serverData['REDIRECT_URL'] : $this->serverData['SCRIPT_NAME']; + } + + if (!$this->hostname) { + $this->hostname = isset($this->serverData['HTTP_HOST']) ? $this->serverData['HTTP_HOST'] : 'No Host'; + } + + $protocol = $this->secure ? 'https' : 'http'; + $this->apiEndPoint = $this->apiEndPoint ?: $protocol.'://'.$this->host.$this->resource; + } + + /** + * Get the combined server parameters. + * + * @return array + */ + public function getParameters() + { + return array_merge($this->get('postData'), $this->get('getData')); + } + + /** + * Verify that the configuration is complete. + */ + public function verify() + { + if (!$this->apiKey) { + throw new AirbrakeException('Cannot initialize the Airbrake client without an ApiKey being set to the configuration.'); + } + } +} diff --git a/src/lib/Airbrake/Connection.php b/src/lib/Airbrake/Connection.php new file mode 100644 index 00000000..80358916 --- /dev/null +++ b/src/lib/Airbrake/Connection.php @@ -0,0 +1,67 @@ + + * @copyright (c) 2011-2013 Drew Butler + * @license http://www.opensource.org/licenses/mit-license.php + */ +class Connection +{ + protected $configuration = null; + protected $headers = array(); + + /** + * Build the object with the airbrake Configuration. + * + * @param Airbrake\Configuration $configuration + */ + public function __construct(Configuration $configuration) + { + $this->configuration = $configuration; + + $this->addHeader(array( + 'Accept: text/xml, application/xml', + 'Content-Type: text/xml' + )); + } + + /** + * Add a header to the connection. + * + * @param string header + */ + public function addHeader($header) + { + $this->headers += (array)$header; + } + + /** + * @param Airbrake\Notice $notice + * @return string + **/ + public function send(Notice $notice) + { + $curl = curl_init(); + + $xml = $notice->toXml($this->configuration); + + curl_setopt($curl, CURLOPT_URL, $this->configuration->apiEndPoint); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_HEADER, 0); + curl_setopt($curl, CURLOPT_TIMEOUT, $this->configuration->timeout); + curl_setopt($curl, CURLOPT_POSTFIELDS, $xml); + curl_setopt($curl, CURLOPT_HTTPHEADER, $this->headers); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); + + $return = curl_exec($curl); + curl_close($curl); + + return $return; + } +} diff --git a/src/lib/Airbrake/EventHandler.php b/src/lib/Airbrake/EventHandler.php new file mode 100644 index 00000000..741d6143 --- /dev/null +++ b/src/lib/Airbrake/EventHandler.php @@ -0,0 +1,182 @@ + + * @copyright (c) 2011-2013 Drew Butler + * @license http://www.opensource.org/licenses/mit-license.php + */ +class EventHandler +{ + /** + * The singleton instance + */ + protected static $instance = null; + protected $airbrakeClient = null; + protected $notifyOnWarning = null; + + protected $warningErrors = array(\E_NOTICE => 'Notice', + \E_STRICT => 'Strict', + \E_USER_WARNING => 'User Warning', + \E_USER_NOTICE => 'User Notice', + \E_DEPRECATED => 'Deprecated', + \E_WARNING => 'Warning', + \E_USER_DEPRECATED => 'User Deprecated', + \E_CORE_WARNING => 'Core Warning', + \E_COMPILE_WARNING => 'Compile Warning', + \E_RECOVERABLE_ERROR => 'Recoverable Error' ); + + protected $fatalErrors = array(\E_ERROR => 'Error', + \E_PARSE => 'Parse', + \E_COMPILE_ERROR => 'Compile Error', + \E_CORE_ERROR => 'Core Error', + \E_USER_ERROR => 'User Error' ); + + /** + * Build with the Airbrake client class. + * + * @param Airbrake\Client $client + */ + public function __construct(Client $client, $notifyOnWarning) + { + $this->notifyOnWarning = $notifyOnWarning; + $this->airbrakeClient = $client; + } + + /** + * Get the current handler. + * + * @param string $apiKey + * @param bool $notifyOnWarning + * @param array $options + * @return EventHandler + */ + public static function start($apiKey, $notifyOnWarning=false, array $options=array()) + { + if ( !isset(self::$instance)) { + $config = new Configuration($apiKey, $options); + + $client = new Client($config); + self::$instance = new self($client, $notifyOnWarning); + + set_error_handler(array(self::$instance, 'onError')); + set_exception_handler(array(self::$instance, 'onException')); + register_shutdown_function(array(self::$instance, 'onShutdown')); + } + + return self::$instance; + } + + + /** + * Revert the handlers back to their original state. + */ + public static function reset() + { + if (isset(self::$instance)) { + restore_error_handler(); + restore_exception_handler(); + } + + self::$instance = null; + } + + /** + * Catches standard PHP style errors + * + * @see http://us3.php.net/manual/en/function.set-error-handler.php + * @param int $type + * @param string $message + * @param string $file + * @param string $line + * @param array $context + * @return bool + */ + public function onError($type, $message, $file = null, $line = null, $context = null) + { + // This will catch silenced @ function calls and keep them quiet. + if (ini_get('error_reporting') == 0) { + return true; + } + + if (isset($this->fatalErrors[$type])) { + throw new Exception($message); + } + + if ($this->notifyOnWarning && isset ($this->warningErrors[$type])) { + // Make sure we pass in the current backtrace, minus this function call. + $backtrace = debug_backtrace(); + array_shift($backtrace); + + $this->airbrakeClient->notifyOnError($message, $backtrace); + return true; + } + + return true; + } + + + /** + * Catches uncaught exceptions. + * + * @see http://us3.php.net/manual/en/function.set-exception-handler.php + * @param Exception $exception + * @return bool + */ + public function onException(Exception $exception) + { + $this->airbrakeClient->notifyOnException($exception); + + return true; + } + + /** + * Handles the PHP shutdown event. + * + * This event exists almost soley to provide a means to catch and log errors that might have been + * otherwise lost when PHP decided to die unexpectedly. + */ + public function onShutdown() + { + // If the instance was unset, then we shouldn't run. + if (self::$instance == null) { + return; + } + + // This will help prevent multiple calls to this, incase the shutdown handler was declared + // multiple times. This only should occur in unit tests, when the handlers are created + // and removed repeatedly. As we cannot remove shutdown handlers, this prevents us from + // calling it 1000 times at the end. + self::$instance = null; + + // Get the last error if there was one, if not, let's get out of here. + if (!$error = error_get_last()) { + return; + } + + // Don't notify on warning if not configured to. + if (!$this->notifyOnWarning && isset($this->warningErrors[$error['type']])) { + return; + } + + // Build a fake backtrace, so we at least can show where we came from. + $backtrace = array( + array( + 'file' => $error['file'], + 'line' => $error['line'], + 'function' => '', + 'args' => array(), + ) + ); + + $this->airbrakeClient->notifyOnError('[Improper Shutdown] '.$error['message'], $backtrace); + } +} diff --git a/src/lib/Airbrake/Exception.php b/src/lib/Airbrake/Exception.php new file mode 100644 index 00000000..36ce6cab --- /dev/null +++ b/src/lib/Airbrake/Exception.php @@ -0,0 +1,15 @@ + + * @copyright (c) 2011-2013 Drew Butler + * @license http://www.opensource.org/licenses/mit-license.php + */ +class Exception extends \Exception +{ + +} diff --git a/src/lib/Airbrake/Notice.php b/src/lib/Airbrake/Notice.php new file mode 100644 index 00000000..47149bb3 --- /dev/null +++ b/src/lib/Airbrake/Notice.php @@ -0,0 +1,109 @@ + + * @copyright (c) 2011-2013 Drew Butler + * @license http://www.opensource.org/licenses/mit-license.php + */ +class Notice extends Record +{ + /** + * The backtrace from the given exception or hash. + */ + protected $_backtrace = null; + + /** + * The name of the class of error (such as RuntimeError) + */ + protected $_errorClass = null; + + /** + * The message from the exception, or a general description of the error + */ + protected $_errorMessage = null; + + /** + * Extra parameters to send to Airbrake + */ + protected $_extraParameters = array(); + + /** + * Convert the notice to xml + * + * @param Airbrake\Configuration $configuration + * @return string + */ + public function toXml(Configuration $configuration) + { + $doc = new SimpleXMLElement(''); + $doc->addAttribute('version', Version::API); + $doc->addChild('api-key', $configuration->get('apiKey')); + + $notifier = $doc->addChild('notifier'); + $notifier->addChild('name', Version::NAME); + $notifier->addChild('version', Version::NUMBER); + $notifier->addChild('url', Version::APP_URL); + + $env = $doc->addChild('server-environment'); + $env->addChild('project-root', $configuration->get('projectRoot')); + $env->addChild('environment-name', $configuration->get('environmentName')); + + $error = $doc->addChild('error'); + $error->addChild('class', $this->errorClass); + $error->addChild('message', htmlspecialchars($this->errorMessage)); + + if (count($this->backtrace) > 0) { + $backtrace = $error->addChild('backtrace'); + foreach ($this->backtrace as $entry) { + $method = isset($entry['class']) ? $entry['class'].'::' : ''; + $method .= isset($entry['function']) ? $entry['function'] : ''; + $line = $backtrace->addChild('line'); + $line->addAttribute('file', isset($entry['file']) ? $entry['file'] : ''); + $line->addAttribute('number', isset($entry['line']) ? $entry['line'] : ''); + $line->addAttribute('method', $method); + } + } + + $request = $doc->addChild('request'); + $request->addChild('url', $configuration->get('url')); + $request->addChild('component', $configuration->get('component')); + $request->addChild('action', $configuration->get('action')); + + $this->array2Node($request, 'params', array_merge($configuration->getParameters(), array('airbrake_extra' => $this->extraParameters))); + $this->array2Node($request, 'session', $configuration->get('sessionData')); + $this->array2Node($request, 'cgi-data', $configuration->get('serverData')); + + return $doc->asXML(); + } + + /** + * Add a Airbrake var block to an XML node. + * + * @param SimpleXMLElement $parentNode + * @param string $key + * @param array $params + **/ + protected function array2Node($parentNode, $key, $params) + { + if (count($params) == 0) { + return; + } + + $node = $parentNode->addChild($key); + foreach ($params as $key => $value) { + if (is_array($value) || is_object($value)) { + $value = json_encode((array) $value); + } + + // htmlspecialchars() is needed to prevent html characters from breaking the node. + $node->addChild('var', htmlspecialchars($value)) + ->addAttribute('key', $key); + } + } +} \ No newline at end of file diff --git a/src/lib/Airbrake/Record.php b/src/lib/Airbrake/Record.php new file mode 100644 index 00000000..977f0423 --- /dev/null +++ b/src/lib/Airbrake/Record.php @@ -0,0 +1,241 @@ + + * class Person extends Record + * { + * protected $_FirstName; + * protected $_LastName; + * protected $_Age; + * } + * + * + * Now you can simply retrieve these properties by requesting them by their key name + * minus the prefixed '_'. So, if you were to call ->get( 'FirstName' ) it would + * retrieve that key for you. Similarly, you can call set( 'FirstName', 'Drew' ) and + * it will set that key. Give load() an array or stdClass of key value pairs and it + * will parse those into their matching keys. Any key that is given that does not + * exist in the parameters will be ignored. + * + * These objects may also be accessed and iterated over as if they were arrays. This means + * that if you prefer the $obj['key'] syntax, you are free to use it. Any keys that are set + * to it that are not known to the record type will be ignored and any that do not exist + * when getting, will simply return null. + * + * @package Airbrake + * @author Drew Butler + * @copyright (c) 2011-2013 Drew Butler + * @license http://www.opensource.org/licenses/mit-license.php + */ +abstract class Record implements ArrayAccess, IteratorAggregate +{ + const PREFIX = '_'; + + + /** + * Load the given data array to the record. + * + * @param array|stdClass $data + */ + public function __construct($data = array()) + { + $this->load($data); + $this->initialize(); + } + + /** + * Get the value for the given key. + * + * The given key should match one of the parameters about, but with out the + * prefix. That is added on during this process. + * + * @param string $key + * @return mixed + */ + public function get($key) + { + if ($this->exists($key)) { + $key = self::PREFIX.$key; + return $this->$key; + } + + return null; + } + + /** + * Magic alias for the get() method. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->get($key); + } + + /** + * Set the given value to the given key. + * + * The given key should match one of the parameters about, but with out the + * prefix. That is added on during this process. + * + * @param string $key + * @param mixed $value + */ + public function set($key, $value) + { + if ($this->exists($key)) { + $key = self::PREFIX.$key; + $this->$key = $value; + } + } + + /** + * Magic alias for the set() method. + * + * @param string $key + * @param mixed $value + */ + public function __set($key, $value) + { + return $this->set($key, $value); + } + + /** + * Load the given data array to the record. + * + * @param array|stdClass $data + */ + public function load($data) + { + if (!is_array($data) && !$data instanceof \stdClass) { + return; + } + + foreach ($data as $key => $value) { + $this->set($key, $value); + } + } + + /** + * Dump the data into an array. + * + * @return array + */ + public function toArray() + { + $data = array(); + $vars = get_object_vars($this); + + foreach ($vars as $key => $value) { + if ($key[0] === self::PREFIX) { + $key = substr($key, 1, strlen($key) - 1); + + if ($value instanceof Record) { + $value = $value->toArray(); + } + + $data[$key] = $value; + } + } + + return $data; + } + + /** + * Is the given key set in this record? + * + * @param string $key + * @return bool + */ + public function exists($key) + { + return property_exists($this, self::PREFIX.$key); + } + + /** + * Get the keys that are contained in this record. + * + * @return array + */ + public function getKeys() + { + return array_keys($this->toArray()); + } + + /** + * Set the given value for the given key + * + * Part of the ArrayAccess interface. + * + * @param string $key + * @param mixed $value + */ + public function offsetSet($key, $value) + { + if (!is_null($key)) { + $this->set($key, $value); + } + } + + /** + * Is the given key available? + * + * Part of the ArrayAccess interface. + * + * @return string $key + * @return bool + */ + public function offsetExists($key) + { + return $this->exists($key); + } + + /** + * Set the given key to null + * + * Part of the ArrayAccess interface. + * + * @param string $key + */ + public function offsetUnset($key) + { + $this->set($key, null); + } + + /** + * Get the value for the given key. + * + * Part of the ArrayAccess interface. + * + * @param string $key + * @return mixed + */ + public function offsetGet($key) + { + return $this->get($key); + } + + /** + * Get the iterator for the IteratorAggregate interface. + * + * @return ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->dump()); + } + + /** + * Optional method to declare that will initialize the data on construct. + */ + protected function initialize() {} +} \ No newline at end of file diff --git a/src/lib/Airbrake/Resque/NotifyJob.php b/src/lib/Airbrake/Resque/NotifyJob.php new file mode 100644 index 00000000..c0a67095 --- /dev/null +++ b/src/lib/Airbrake/Resque/NotifyJob.php @@ -0,0 +1,18 @@ +args['notice']); + $configuration = unserialize($this->args['configuration']); + + $connection = new Connection($configuration); + echo $connection->send($notice); + } +} diff --git a/src/lib/Airbrake/Version.php b/src/lib/Airbrake/Version.php new file mode 100644 index 00000000..67a422f9 --- /dev/null +++ b/src/lib/Airbrake/Version.php @@ -0,0 +1,18 @@ + + * @copyright (c) 2011-2013 Drew Butler + * @license http://www.opensource.org/licenses/mit-license.php + */ +class Version +{ + const NAME = 'nodrew-php-airbrake'; + const NUMBER = '1.0'; + const APP_URL = 'https://github.com/nodrew/php-airbrake'; + const API = '2.0'; +} From b94af6cb2e43bac43480ffdd1ec26190617cb59b Mon Sep 17 00:00:00 2001 From: Lucas van Staden Date: Mon, 8 May 2017 12:51:05 +0800 Subject: [PATCH 2/6] Update note to airbrake --- src/app/code/community/FireGento/Logger/etc/config.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/code/community/FireGento/Logger/etc/config.xml b/src/app/code/community/FireGento/Logger/etc/config.xml index 90b306af..0d0e365b 100644 --- a/src/app/code/community/FireGento/Logger/etc/config.xml +++ b/src/app/code/community/FireGento/Logger/etc/config.xml @@ -158,10 +158,10 @@ FireGento_Logger_Model_Redis - + FireGento_Logger_Model_Airbrake - + From 59f71547063a3ea63c1456c0206bd60bafbae2e0 Mon Sep 17 00:00:00 2001 From: Sergey Kalenyuk Date: Fri, 14 Jul 2017 09:36:17 +0200 Subject: [PATCH 3/6] Add admin user id and admin user name to the event metadata. --- src/app/code/community/FireGento/Logger/Helper/Data.php | 7 +++++++ src/app/code/community/FireGento/Logger/Model/Event.php | 4 ++++ src/app/code/community/FireGento/Logger/etc/system.xml | 2 ++ 3 files changed, 13 insertions(+) diff --git a/src/app/code/community/FireGento/Logger/Helper/Data.php b/src/app/code/community/FireGento/Logger/Helper/Data.php index 86c29046..4be13743 100644 --- a/src/app/code/community/FireGento/Logger/Helper/Data.php +++ b/src/app/code/community/FireGento/Logger/Helper/Data.php @@ -130,6 +130,13 @@ public function addEventMetadata(&$event, $notAvailable = null, $enableBacktrace ->setLine($notAvailable) ->setBacktrace($notAvailable) ->setStoreCode(Mage::app()->getStore()->getCode()); + if (Mage::app()->getStore()->isAdmin() && isset($_SESSION)) { + $session = Mage::getSingleton('admin/session'); + if ($session->isLoggedIn()) { + $event->setAdminUserId($session->getUserId()); + $event->setAdminUserName($session->getUser()->getName()); + } + } // Add request time if (isset($_SERVER['REQUEST_TIME_FLOAT'])) { diff --git a/src/app/code/community/FireGento/Logger/Model/Event.php b/src/app/code/community/FireGento/Logger/Model/Event.php index a3437a47..4679217d 100644 --- a/src/app/code/community/FireGento/Logger/Model/Event.php +++ b/src/app/code/community/FireGento/Logger/Model/Event.php @@ -37,6 +37,10 @@ * @method $this setRequestUri(string $value) * @method string getStoreCode() * @method $this setStoreCode(string $value) + * @method int getAdminUserId() + * @method $this setAdminUserId(int $value) + * @method string getAdminUserName() + * @method $this setAdminUserName(string $value) * @method string getHttpUserAgent() * @method $this setHttpUserAgent(string $value) * @method string getHttpCookie() diff --git a/src/app/code/community/FireGento/Logger/etc/system.xml b/src/app/code/community/FireGento/Logger/etc/system.xml index 4ad04ed2..a948bea9 100644 --- a/src/app/code/community/FireGento/Logger/etc/system.xml +++ b/src/app/code/community/FireGento/Logger/etc/system.xml @@ -71,6 +71,8 @@ %priorityName%
%priority%
%storeCode%
+ %adminUserId%
+ %adminUserName%
%timeElapsed%
%hostname%
%requestMethod%
From 901e61cf97a7c11f8489b68aa923c612e3cf8823 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 20 Jul 2017 18:12:14 +0200 Subject: [PATCH 4/6] Little update which corrects a bug which prevents Exceptions to be written to Graylog --- src/app/code/community/FireGento/Logger/Model/Graylog2.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/code/community/FireGento/Logger/Model/Graylog2.php b/src/app/code/community/FireGento/Logger/Model/Graylog2.php index 1aeca7e1..8dcdbc92 100644 --- a/src/app/code/community/FireGento/Logger/Model/Graylog2.php +++ b/src/app/code/community/FireGento/Logger/Model/Graylog2.php @@ -112,7 +112,7 @@ protected function _write($event) Mage::helper('firegento_logger')->addEventMetadata($event); - $message = $event->getMessage(); + $message = trim($event->getMessage()); $eofMessageFirstLine = strpos($message, "\n"); $shortMessage = (false === $eofMessageFirstLine) ? $message : From b5e347e7f5c15bb9a7d2cc327daa3cd8def8651e Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 20 Jul 2017 18:15:15 +0200 Subject: [PATCH 5/6] Correct spaces --- src/app/code/community/FireGento/Logger/Model/Graylog2.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/code/community/FireGento/Logger/Model/Graylog2.php b/src/app/code/community/FireGento/Logger/Model/Graylog2.php index 8dcdbc92..7bfd8185 100644 --- a/src/app/code/community/FireGento/Logger/Model/Graylog2.php +++ b/src/app/code/community/FireGento/Logger/Model/Graylog2.php @@ -112,7 +112,7 @@ protected function _write($event) Mage::helper('firegento_logger')->addEventMetadata($event); - $message = trim($event->getMessage()); + $message = trim($event->getMessage()); $eofMessageFirstLine = strpos($message, "\n"); $shortMessage = (false === $eofMessageFirstLine) ? $message : From e084a2305262ed8f19d2a67c979c53f420519c9b Mon Sep 17 00:00:00 2001 From: Colin Mollenhour Date: Mon, 7 Aug 2017 14:15:45 -0400 Subject: [PATCH 6/6] Session should never be started by logger. Fixes #106 --- src/app/code/community/FireGento/Logger/Model/Logstash.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/app/code/community/FireGento/Logger/Model/Logstash.php b/src/app/code/community/FireGento/Logger/Model/Logstash.php index 97a1676a..c431b8a8 100644 --- a/src/app/code/community/FireGento/Logger/Model/Logstash.php +++ b/src/app/code/community/FireGento/Logger/Model/Logstash.php @@ -107,9 +107,8 @@ protected function buildJSONMessage($event, $enableBacktrace = false) /** this prevents different datatypes as getHttpHost() returns either string or boolean (false) */ $fields['HttpHost'] = (!Mage::app()->getRequest()->getHttpHost()) ? 'cli': Mage::app()->getRequest()->getHttpHost(); $fields['LogFileName'] = $this->_logFileName; - /** This is to prevent infinite loops with Cm_Redis_Session because the constructor calls the logging if - * log_level >= Zend_Log::Debug */ - if ((int) Mage::getConfig()->getNode('global/redis_session')->descend('log_level') < Zend_Log::DEBUG) { + // Only add session fields if a session was already instantiated and logger should not start a new session + if (isset($_SESSION)) { $fields['SessionId'] = Mage::getSingleton("core/session")->getEncryptedSessionId(); $fields['CustomerId'] = Mage::getSingleton('customer/session')->getCustomerId(); }