diff --git a/raven/.gitattributes b/raven/.gitattributes new file mode 100755 index 0000000..33a59ae --- /dev/null +++ b/raven/.gitattributes @@ -0,0 +1,3 @@ +/examples export-ignore +/docs export-ignore +/test export-ignore diff --git a/raven/.gitignore b/raven/.gitignore old mode 100644 new mode 100755 diff --git a/raven/.gitmodules b/raven/.gitmodules old mode 100644 new mode 100755 diff --git a/raven/.php_cs b/raven/.php_cs old mode 100644 new mode 100755 diff --git a/raven/.travis.yml b/raven/.travis.yml old mode 100644 new mode 100755 index 8182db2..de0d100 --- a/raven/.travis.yml +++ b/raven/.travis.yml @@ -6,12 +6,13 @@ php: - 5.5 - 5.6 - 7.0 + - nightly - hhvm matrix: allow_failures: - php: hhvm - - php: 7.0 + - php: nightly fast_finish: true sudo: false @@ -21,10 +22,12 @@ cache: - $HOME/.composer/cache before_install: + - if [[ ${TRAVIS_PHP_VERSION:0:1} > "5" ]]; then pecl install uopz; fi + - if [[ ${TRAVIS_PHP_VERSION:0:1} > "5" ]]; then echo "extension=uopz.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi - composer self-update install: travis_retry composer install --no-interaction --prefer-source script: - vendor/bin/php-cs-fixer fix --config-file=.php_cs --verbose --diff --dry-run - - vendor/bin/phpunit + - vendor/bin/phpunit --verbose diff --git a/raven/AUTHORS b/raven/AUTHORS old mode 100644 new mode 100755 diff --git a/raven/CHANGES b/raven/CHANGES old mode 100644 new mode 100755 index da768e5..094402d --- a/raven/CHANGES +++ b/raven/CHANGES @@ -1,3 +1,122 @@ +1.6.2 +----- + +- Fixed behavior where fatal errors weren't correctly being reported in most situations. + + +1.6.1 +----- + +- Correct handling of null in ``user_context``. + +1.6.0 +----- + +- Improved serialization of certain types to be more restrictive. +- ``error_types`` can now be configured via ``RavenClient``. +- Class serialization has been expanded to include attributes. +- The session extension is no longer required. +- Monolog is no longer a required dependency. +- ``user_context`` now merges by default. + +1.5.0 +----- + +- Added named transaction support. + +1.4.0 +----- + +This version primarily overhauls the exception/stacktrace generation to fix +a few bugs and improve the quality of data (#359). + +- Added ``excluded_app_paths`` config. +- Removed ``shift_vars`` config. +- Correct fatal error handling to only operate on expected types. This also + fixes some behavior with the error suppression operator. +- Expose anonymous and similar frames in the stacktrace. +- Default ``prefixes`` to PHP's include paths. +- Remove ``module`` usage. +- Better handle empty argument context. +- Correct alignment of filename (current frame) and function (caller frame) + +1.3.0 +----- + +- Fixed an issue causing the error suppression operator to not be respected (#335) +- Fixed some serialization behavior (#352) +- Fixed an issue with app paths and trailing slashes (#350) +- Handle non-latin encoding with source code context line (#345) + +1.2.0 +----- + +- Handle non-latin encoding in source code and exception values (#342) +- Ensure pending events are sent on shutdown by default (#338) +- Add ``captureLastError`` helper (#334) +- Dont report duplicate errors with fatal error handler (#334) +- Enforce maximum length for string serialization (#329) + +1.1.0 +----- + +- Uncoercable values should no longer prevent exceptions from sending + to the Sentry server. +- ``install()`` can no longer be called multiple times. + +1.0.0 +----- + +- Removed deprecated error codes configuration from ErrorHandler. +- Removed env data from HTTP interface. +- Removed 'message' attribute from exceptions'. +- appPath and prefixes are now resolved fully. +- Fixed various getter methods requiring invalid args. +- Fixed data mutation with 'send_callback'. + +0.22.0 +------ + +- Improve handling of encodings. +- Improve resiliency of variable serialization. +- Add 'formatted' attribute to Message interface. + +0.21.0 +------ + +- Added ``transport`` option. +- Added ``install()`` shortcut. + +0.20.0 +------ + +- Handle missing function names on frames. +- Remove suppression operator usage in breadcrumbs buffer. +- Force serialization of context values. + +0.19.0 +------ + +- Add error_reporting breadcrumb handler. + +0.18.0 +------ + +- Remove session from serialized data. +- send_callback return value must now be false to prevent capture. +- Add various getter/setter methods for configuration. + +0.17.0 +------ + +- Don't attempt to serialize fixed SDK inputs. +- Improvements to breadcrumbs support in Monolog. + +0.16.0 +------ + +- Initial breadcrumbs support with Monolog handler. + 0.15.0 ------ diff --git a/raven/LICENSE b/raven/LICENSE old mode 100644 new mode 100755 diff --git a/raven/Makefile b/raven/Makefile old mode 100644 new mode 100755 diff --git a/raven/README.rst b/raven/README.rst old mode 100644 new mode 100755 index 9da1bb3..b24f25f --- a/raven/README.rst +++ b/raven/README.rst @@ -9,264 +9,41 @@ The official PHP SDK for `Sentry `_. .. code-block:: php - // Instantiate a new client with a compatible DSN - $client = new Raven_Client('http://public:secret@example.com/1'); - - // Capture a message - $event_id = $client->getIdent($client->captureMessage('my log message')); - if ($client->getLastError() !== null) { - printf('There was an error sending the event to Sentry: %s', $client->getLastError()); - } + // Instantiate a new client with a compatible DSN and install built-in + // handlers + $client = (new Raven_Client('http://public:secret@example.com/1'))->install(); // Capture an exception - $event_id = $client->getIdent($client->captureException($ex)); - - // Provide some additional data with an exception - $event_id = $client->getIdent($client->captureException($ex, array( - 'extra' => array( - 'php_version' => phpversion() - ), - ))); + $event_id = $client->captureException($ex); // Give the user feedback echo "Sorry, there was an error!"; echo "Your reference ID is " . $event_id; - // Install error handlers and shutdown function to catch fatal errors - $error_handler = new Raven_ErrorHandler($client); - $error_handler->registerExceptionHandler(); - $error_handler->registerErrorHandler(); - $error_handler->registerShutdownFunction(); - -Installation ------------- - -Install with Composer -~~~~~~~~~~~~~~~~~~~~~ - -If you're using `Composer `_ to manage -dependencies, you can add Raven with it. - -:: - - $ composer require sentry/sentry:$VERSION - -(replace ``$VERSION`` with one of the available versions on `Packagist `_) -or to get the latest version off the master branch: - -:: - - $ composer require sentry/sentry:dev-master - -Note that using unstable versions is not recommended and should be avoided. Also -you should define a maximum version, e.g. by doing ``>=0.6,<1.0`` or ``~0.6``. - -Alternatively, use the ``^`` operator for specifying a version, e.g., - -:: - - $ composer require sentry/sentry:^0.11.0 - -Composer will take care of the autoloading for you, so if you require the -``vendor/autoload.php``, you're good to go. - - -Install source from GitHub -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To install the source code: - -:: - - $ git clone git://github.com/getsentry/sentry-php.git - -And including it using the autoloader: - -.. code-block:: php - - require_once '/path/to/Raven/library/Raven/Autoloader.php'; - Raven_Autoloader::register(); - -Testing Your Connection ------------------------ - -The PHP client includes a simple helper script to test your connection and credentials with -the Sentry master server: - -.. code-block:: bash - - $ bin/sentry test https://public:secret@app.getsentry.com/1 - Client configuration: - -> server: [https://sentry.example.com/api/store/] - -> project: 1 - -> public_key: public - -> secret_key: secret - - Sending a test event: - -> event ID: f1765c9aed4f4ceebe5a93df9eb2d34f - - Done! - -.. note:: The CLI enforces the synchronous option on HTTP requests whereas the default configuration is asyncrhonous. - -Configuration -------------- - -Several options exist that allow you to configure the behavior of the ``Raven_Client``. These are passed as the -second parameter of the constructor, and is expected to be an array of key value pairs: - -.. code-block:: php - - $client = new Raven_Client($dsn, array( - 'option_name' => 'value', - )); - -``name`` -~~~~~~~~ - -A string to override the default value for the server's hostname. - -Defaults to ``Raven_Compat::gethostname()``. - -``tags`` -~~~~~~~~ - -An array of tags to apply to events in this context. - -.. code-block:: php - - 'tags' => array( - 'php_version' => phpversion(), - ) - - -``curl_method`` -~~~~~~~~~~~~~~~ - -Defaults to 'sync'. - -Available methods: - -- sync (default): send requests immediately when they're made -- async: uses a curl_multi handler for best-effort asynchronous submissions -- exec: asynchronously send events by forking a curl process for each item - -``curl_path`` -~~~~~~~~~~~~~ - -Defaults to 'curl'. - -Specify the path to the curl binary to be used with the 'exec' curl method. - - -``trace`` -~~~~~~~~~ - -Set this to ``false`` to disable reflection tracing (function calling arguments) in stacktraces. - - -``logger`` -~~~~~~~~~~ - -Adjust the default logger name for messages. - -Defaults to ``php``. - -``ca_cert`` -~~~~~~~~~~~ - -The path to the CA certificate bundle. - -Defaults to the common bundle which includes getsentry.com: ./data/cacert.pem - -Caveats: - -- The CA bundle is ignored unless curl throws an error suggesting it needs a cert. -- The option is only currently used within the synchronous curl transport. - -``curl_ssl_version`` -~~~~~~~~~~~~~~~~~~~~ - -The SSL version (2 or 3) to use. -By default PHP will try to determine this itself, although in some cases this must be set manually. - -``message_limit`` -~~~~~~~~~~~~~~~~~ - -Defaults to 1024 characters. - -This value is used to truncate message and frame variables. However it is not guarantee that length of whole message will be restricted by this value. - -``processors`` -~~~~~~~~~~~~~~~~~ - -An array of classes to use to process data before it is sent to Sentry. By default, Raven_SanitizeDataProcessor is used - -``processorOptions`` -~~~~~~~~~~~~~~~~~ -Options that will be passed on to a setProcessorOptions() function in a Raven_Processor sub-class before that Processor is added to the list of processors used by Raven_Client - -An example of overriding the regular expressions in Raven_SanitizeDataProcessor is below: - -.. code-block:: php - - 'processorOptions' => array( - 'Raven_SanitizeDataProcessor' => array( - 'fields_re' => '/(user_password|user_token|user_secret)/i', - 'values_re' => '/^(?:\d[ -]*?){15,16}$/' - ) - ) - -Providing Request Context -------------------------- - -Most of the time you're not actually calling out to Raven directly, but you still want to provide some additional context. This lifecycle generally constists of something like the following: - -- Set some context via a middleware (e.g. the logged in user) -- Send all given context with any events during the request lifecycle -- Cleanup context - -There are three primary methods for providing request context: - -.. code-block:: php - - // bind the logged in user - $client->user_context(array('email' => 'foo@example.com')); - - // tag the request with something interesting - $client->tags_context(array('interesting' => 'yes')); - - // provide a bit of additional context - $client->extra_context(array('happiness' => 'very')); - - -If you're performing additional requests during the lifecycle, you'll also need to ensure you cleanup the context (to reset its state): - -.. code-block:: php - - $client->context->clear(); +For more information, see our `documentation `_. Contributing ------------ -First, make sure you can run the test suite. Install development dependencies : +Dependencies are managed through composer: :: $ composer install -You may now use phpunit : + +Tests can then be run via phpunit: :: $ vendor/bin/phpunit - Resources --------- +* `Documentation `_ * `Bug Tracker `_ * `Code `_ * `Mailing List `_ diff --git a/raven/bin/sentry b/raven/bin/sentry index fddc0a8..4694cfc 100755 --- a/raven/bin/sentry +++ b/raven/bin/sentry @@ -76,6 +76,10 @@ function cmd_test($dsn) function main() { global $argv; + if (!isset($argv[1])) { + exit('Usage: sentry test '); + } + $cmd = $argv[1]; switch ($cmd) { @@ -83,7 +87,7 @@ function main() { cmd_test(@$argv[2]); break; default: - exit('Usage: raven test '); + exit('Usage: sentry test '); } } diff --git a/raven/composer.json b/raven/composer.json old mode 100644 new mode 100755 index 1f6137d..a34bd16 --- a/raven/composer.json +++ b/raven/composer.json @@ -12,13 +12,19 @@ } ], "require-dev": { - "fabpot/php-cs-fixer": "^1.8.0", - "phpunit/phpunit": "^4.6.6" + "friendsofphp/php-cs-fixer": "^1.8.0", + "phpunit/phpunit": "^4.8 || ^5.0", + "monolog/monolog": "*" }, "require": { "php": ">=5.2.4", - "ext-curl": "*", - "monolog/monolog": "*" + "ext-curl": "*" + }, + "suggest": { + "monolog/monolog": "Automatically capture Monolog events as breadcrumbs" + }, + "conflict": { + "raven/raven": "*" }, "bin": [ "bin/sentry" @@ -30,7 +36,7 @@ }, "extra": { "branch-alias": { - "dev-master": "0.17.x-dev" + "dev-master": "1.6.x-dev" } } } diff --git a/raven/lib/Raven/Autoloader.php b/raven/lib/Raven/Autoloader.php old mode 100644 new mode 100755 diff --git a/raven/lib/Raven/Breadcrumbs.php b/raven/lib/Raven/Breadcrumbs.php old mode 100644 new mode 100755 index a07e900..d1a9946 --- a/raven/lib/Raven/Breadcrumbs.php +++ b/raven/lib/Raven/Breadcrumbs.php @@ -38,10 +38,9 @@ public function fetch() { $results = array(); for ($i = 0; $i <= ($this->size - 1); $i++) { - $node = @$this->buffer[($this->pos + $i) % $this->size]; - - if (isset($node)) { - $results[] = $node; + $idx = ($this->pos + $i) % $this->size; + if (isset($this->buffer[$idx])) { + $results[] = $this->buffer[$idx]; } } return $results; diff --git a/raven/lib/Raven/Breadcrumbs/ErrorHandler.php b/raven/lib/Raven/Breadcrumbs/ErrorHandler.php new file mode 100755 index 0000000..d48ab1d --- /dev/null +++ b/raven/lib/Raven/Breadcrumbs/ErrorHandler.php @@ -0,0 +1,47 @@ +ravenClient = $ravenClient; + } + + public function handleError($code, $message, $file = '', $line = 0, $context=array()) + { + $this->ravenClient->breadcrumbs->record(array( + 'category' => 'error_reporting', + 'message' => $message, + 'level' => $this->ravenClient->translateSeverity($code), + 'data' => array( + 'code' => $code, + 'line' => $line, + 'file' => $file, + ), + )); + + if ($this->existingHandler !== null) { + return call_user_func($this->existingHandler, $code, $message, $file, $line, $context); + } else { + return false; + } + } + + public function install() + { + $this->existingHandler = set_error_handler(array($this, 'handleError'), E_ALL); + return $this; + } +} diff --git a/raven/lib/Raven/Breadcrumbs/MonologHandler.php b/raven/lib/Raven/Breadcrumbs/MonologHandler.php old mode 100644 new mode 100755 diff --git a/raven/lib/Raven/Client.php b/raven/lib/Raven/Client.php old mode 100644 new mode 100755 index f13918b..cb607e7 --- a/raven/lib/Raven/Client.php +++ b/raven/lib/Raven/Client.php @@ -16,7 +16,8 @@ class Raven_Client { - const VERSION = '0.17.0'; + const VERSION = '1.6.2'; + const PROTOCOL = '6'; const DEBUG = 'debug'; @@ -28,11 +29,18 @@ class Raven_Client const MESSAGE_LIMIT = 1024; - public $severity_map; + public $breadcrumbs; + public $context; public $extra_data; - + public $severity_map; public $store_errors_for_bulk_send = false; + protected $error_handler; + protected $error_types; + + protected $serializer; + protected $reprSerializer; + public function __construct($options_or_dsn=null, $options=array()) { if (is_array($options_or_dsn)) { @@ -69,37 +77,194 @@ public function __construct($options_or_dsn=null, $options=array()) $this->message_limit = Raven_Util::get($options, 'message_limit', self::MESSAGE_LIMIT); $this->exclude = Raven_Util::get($options, 'exclude', array()); $this->severity_map = null; - $this->shift_vars = (bool) Raven_Util::get($options, 'shift_vars', true); $this->http_proxy = Raven_Util::get($options, 'http_proxy'); $this->extra_data = Raven_Util::get($options, 'extra', array()); $this->send_callback = Raven_Util::get($options, 'send_callback', null); $this->curl_method = Raven_Util::get($options, 'curl_method', 'sync'); $this->curl_path = Raven_Util::get($options, 'curl_path', 'curl'); - $this->curl_ipv4 = Raven_util::get($options, 'curl_ipv4', true); + $this->curl_ipv4 = Raven_Util::get($options, 'curl_ipv4', true); $this->ca_cert = Raven_Util::get($options, 'ca_cert', $this->get_default_ca_cert()); $this->verify_ssl = Raven_Util::get($options, 'verify_ssl', true); $this->curl_ssl_version = Raven_Util::get($options, 'curl_ssl_version'); $this->trust_x_forwarded_proto = Raven_Util::get($options, 'trust_x_forwarded_proto'); - // a list of prefixes used to coerce absolute paths into relative - $this->prefixes = Raven_Util::get($options, 'prefixes', null); - // app path is used to determine if code is part of your application - $this->app_path = Raven_Util::get($options, 'app_path', null); + $this->transport = Raven_Util::get($options, 'transport', null); + $this->mb_detect_order = Raven_Util::get($options, 'mb_detect_order', null); + $this->error_types = Raven_Util::get($options, 'error_types', null); + // app path is used to determine if code is part of your application + $this->setAppPath(Raven_Util::get($options, 'app_path', null)); + $this->setExcludedAppPaths(Raven_Util::get($options, 'excluded_app_paths', null)); + // a list of prefixes used to coerce absolute paths into relative + $this->setPrefixes(Raven_Util::get($options, 'prefixes', $this->getDefaultPrefixes())); $this->processors = $this->setProcessorsFromOptions($options); $this->_lasterror = null; $this->_last_event_id = null; $this->_user = null; + $this->_pending_events = array(); $this->context = new Raven_Context(); $this->breadcrumbs = new Raven_Breadcrumbs(); + $this->sdk = Raven_Util::get($options, 'sdk', array( 'name' => 'sentry-php', 'version' => self::VERSION, )); + $this->serializer = new Raven_Serializer($this->mb_detect_order); + $this->reprSerializer = new Raven_ReprSerializer($this->mb_detect_order); if ($this->curl_method == 'async') { $this->_curl_handler = new Raven_CurlHandler($this->get_curl_options()); } + + $this->transaction = new Raven_TransactionStack(); + if ($this->is_http_request() && isset($_SERVER['PATH_INFO'])) { + $this->transaction->push($_SERVER['PATH_INFO']); + } + + if (Raven_Util::get($options, 'install_default_breadcrumb_handlers', true)) { + $this->registerDefaultBreadcrumbHandlers(); + } + + register_shutdown_function(array($this, 'onShutdown')); + } + + /** + * Installs any available automated hooks (such as error_reporting). + */ + public function install() + { + if ($this->error_handler) { + throw new Raven_Exception(sprintf('%s->install() must only be called once', get_class($this))); + } + $this->error_handler = new Raven_ErrorHandler($this, false, $this->error_types); + $this->error_handler->registerExceptionHandler(); + $this->error_handler->registerErrorHandler(); + $this->error_handler->registerShutdownFunction(); + return $this; + } + + public function getRelease() + { + return $this->release; + } + + public function setRelease($value) + { + $this->release = $value; + return $this; + } + + public function getEnvironment() + { + return $this->environment; + } + + public function setEnvironment($value) + { + $this->environment = $value; + return $this; + } + + private function getDefaultPrefixes() + { + $value = get_include_path(); + return explode(':', $value); + } + + private function _convertPath($value) + { + $path = @realpath($value); + if ($path === false) { + $path = $value; + } + // we need app_path to have a trailing slash otherwise + // base path detection becomes complex if the same + // prefix is matched + if (substr($path, 0, 1) === '/' && substr($path, -1, 1) !== '/') { + $path = $path . '/'; + } + return $path; + } + + public function getAppPath() + { + return $this->app_path; + } + + public function setAppPath($value) + { + if ($value) { + $this->app_path = $this->_convertPath($value); + } else { + $this->app_path = null; + } + return $this; + } + + public function getExcludedAppPaths() + { + return $this->excluded_app_paths; + } + + public function setExcludedAppPaths($value) + { + if ($value) { + $this->excluded_app_paths = $value ? array_map(array($this, '_convertPath'), $value) : $value; + } else { + $this->excluded_app_paths = null; + } + return $this; + } + public function getPrefixes() + { + return $this->prefixes; + } + + public function setPrefixes($value) + { + $this->prefixes = $value ? array_map(array($this, '_convertPath'), $value) : $value; + return $this; + } + + public function getSendCallback() + { + return $this->send_callback; + } + + public function setSendCallback($value) + { + $this->send_callback = $value; + return $this; + } + + public function getTransport() + { + return $this->transport; + } + + public function getServerEndpoint($value) + { + return $this->server; + } + + public function getUserAgent() + { + return 'sentry-php/' . self::VERSION; + } + + /** + * Set a custom transport to override how Sentry events are sent upstream. + * + * The bound function will be called with ``$client`` and ``$data`` arguments + * and is responsible for encoding the data, authenticating, and sending + * the data to the upstream Sentry server. + * + * @param function $value Function to be called + */ + public function setTransport($value) + { + $this->transport = $value; + return $this; } public static function getDefaultProcessors() @@ -208,8 +373,12 @@ public function exception($exception) /** * Log a message to sentry + * + * @param string $message The message (primary description) for the event. + * @param array $params params to use when formatting the message. + * @param array $data Additional attributes to pass with this event (see Sentry docs). */ - public function captureMessage($message, $params=array(), $level_or_options=array(), + public function captureMessage($message, $params=array(), $data=array(), $stack=false, $vars = null) { // Gracefully handle messages which contain formatting characters, but were not @@ -220,20 +389,20 @@ public function captureMessage($message, $params=array(), $level_or_options=arra $formatted_message = $message; } - if ($level_or_options === null) { + if ($data === null) { $data = array(); - } elseif (!is_array($level_or_options)) { + // support legacy method of passing in a level name as the third arg + } elseif (!is_array($data)) { $data = array( - 'level' => $level_or_options, + 'level' => $data, ); - } else { - $data = $level_or_options; } $data['message'] = $formatted_message; $data['sentry.interfaces.Message'] = array( 'message' => $message, 'params' => $params, + 'formatted' => $formatted_message, ); return $this->capture($data, $stack, $vars); @@ -241,8 +410,11 @@ public function captureMessage($message, $params=array(), $level_or_options=arra /** * Log an exception to sentry + * + * @param Exception $exception The Exception object. + * @param array $data Additional attributes to pass with this event (see Sentry docs). */ - public function captureException($exception, $culprit_or_options=null, $logger=null, $vars=null) + public function captureException($exception, $data=null, $logger=null, $vars=null) { $has_chained_exceptions = version_compare(PHP_VERSION, '5.3.0', '>='); @@ -250,27 +422,15 @@ public function captureException($exception, $culprit_or_options=null, $logger=n return null; } - if (!is_array($culprit_or_options)) { + if ($data === null) { $data = array(); - if ($culprit_or_options !== null) { - $data['culprit'] = $culprit_or_options; - } - } else { - $data = $culprit_or_options; - } - - // TODO(dcramer): DRY this up - $message = $exception->getMessage(); - if (empty($message)) { - $message = get_class($exception); } $exc = $exception; do { $exc_data = array( - 'value' => $exc->getMessage(), + 'value' => $this->serializer->serialize($exc->getMessage()), 'type' => get_class($exc), - 'module' => $exc->getFile() .':'. $exc->getLine(), ); /**'exception' @@ -292,15 +452,14 @@ public function captureException($exception, $culprit_or_options=null, $logger=n $exc_data['stacktrace'] = array( 'frames' => Raven_Stacktrace::get_stack_info( - $trace, $this->trace, $this->shift_vars, $vars, $this->message_limit, $this->prefixes, - $this->app_path + $trace, $this->trace, $vars, $this->message_limit, $this->prefixes, + $this->app_path, $this->excluded_app_paths, $this->serializer, $this->reprSerializer ), ); $exceptions[] = $exc_data; } while ($has_chained_exceptions && $exc = $exc->getPrevious()); - $data['message'] = $message; $data['exception'] = array( 'values' => array_reverse($exceptions), ); @@ -319,6 +478,24 @@ public function captureException($exception, $culprit_or_options=null, $logger=n return $this->capture($data, $trace, $vars); } + + /** + * Capture the most recent error (obtained with ``error_get_last``). + */ + public function captureLastError() + { + if (null === $error = error_get_last()) { + return; + } + + $e = new ErrorException( + @$error['message'], 0, @$error['type'], + @$error['file'], @$error['line'] + ); + + return $this->captureException($e); + } + /** * Log an query to sentry */ @@ -346,6 +523,12 @@ public function getLastEventID() return $this->_last_event_id; } + protected function registerDefaultBreadcrumbHandlers() + { + $handler = new Raven_Breadcrumbs_ErrorHandler($this); + $handler->install(); + } + protected function is_http_request() { return isset($_SERVER['REQUEST_METHOD']) && PHP_SAPI !== 'cli'; @@ -353,15 +536,13 @@ protected function is_http_request() protected function get_http_data() { - $env = $headers = array(); + $headers = array(); foreach ($_SERVER as $key => $value) { if (0 === strpos($key, 'HTTP_')) { $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))))] = $value; } elseif (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH')) && $value !== '') { $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key))))] = $value; - } else { - $env[$key] = $value; } } @@ -382,9 +563,6 @@ protected function get_http_data() if (!empty($headers)) { $result['headers'] = $headers; } - if (!empty($env)) { - $result['env'] = $env; - } return array( 'request' => $result, @@ -395,7 +573,7 @@ protected function get_user_data() { $user = $this->context->user; if ($user === null) { - if (!session_id()) { + if (!function_exists('session_id') || !session_id()) { return array(); } $user = array( @@ -425,6 +603,7 @@ public function get_default_data() 'tags' => $this->tags, 'platform' => 'php', 'sdk' => $this->sdk, + 'culprit' => $this->transaction->peek(), ); } @@ -477,20 +656,15 @@ public function capture($data, $stack = null, $vars = null) if (empty($data['extra'])) { unset($data['extra']); - } else { - $data['extra'] = $data['extra']; } - if (empty($data['tags'])) { unset($data['tags']); - } else { - $data['tags'] = $data['tags']; } - if (!empty($data['user'])) { - $data['user'] = $data['user']; + if (empty($data['user'])) { + unset($data['user']); } - if (!empty($data['request'])) { - $data['request'] = $data['request']; + if (empty($data['request'])) { + unset($data['request']); } if (!$this->breadcrumbs->is_empty()) { @@ -513,8 +687,8 @@ public function capture($data, $stack = null, $vars = null) if (!isset($data['stacktrace']) && !isset($data['exception'])) { $data['stacktrace'] = array( 'frames' => Raven_Stacktrace::get_stack_info( - $stack, $this->trace, $this->shift_vars, $vars, $this->message_limit, - $this->prefixes, $this->app_path + $stack, $this->trace, $vars, $this->message_limit, $this->prefixes, + $this->app_path, $this->excluded_app_paths, $this->serializer, $this->reprSerializer ), ); } @@ -526,10 +700,7 @@ public function capture($data, $stack = null, $vars = null) if (!$this->store_errors_for_bulk_send) { $this->send($data); } else { - if (empty($this->error_data)) { - $this->error_data = array(); - } - $this->error_data[] = $data; + $this->_pending_events[] = $data; } $this->_last_event_id = $data['event_id']; @@ -539,6 +710,24 @@ public function capture($data, $stack = null, $vars = null) public function sanitize(&$data) { + // attempt to sanitize any user provided data + if (!empty($data['request'])) { + $data['request'] = $this->serializer->serialize($data['request']); + } + if (!empty($data['user'])) { + $data['user'] = $this->serializer->serialize($data['user'], 3); + } + if (!empty($data['extra'])) { + $data['extra'] = $this->serializer->serialize($data['extra']); + } + if (!empty($data['tags'])) { + foreach ($data['tags'] as $key => $value) { + $data['tags'][$key] = @(string)$value; + } + } + if (!empty($data['contexts'])) { + $data['contexts'] = $this->serializer->serialize($data['contexts'], 5); + } } /** @@ -555,27 +744,47 @@ public function process(&$data) public function sendUnsentErrors() { - if (!empty($this->error_data)) { - foreach ($this->error_data as $data) { - $this->send($data); - } - unset($this->error_data); + foreach ($this->_pending_events as $data) { + $this->send($data); } + $this->_pending_events = array(); if ($this->store_errors_for_bulk_send) { //in case an error occurs after this is called, on shutdown, send any new errors. $this->store_errors_for_bulk_send = !defined('RAVEN_CLIENT_END_REACHED'); } } + public function encode(&$data) + { + $message = Raven_Compat::json_encode($data); + if ($message === false) { + if (function_exists('json_last_error_msg')) { + $this->_lasterror = json_last_error_msg(); + } else { + $this->_lasterror = json_last_error(); + } + return false; + } + + if (function_exists("gzcompress")) { + $message = gzcompress($message); + } + + // PHP's builtin curl_* function are happy without this, but the exec method requires it + $message = base64_encode($message); + + return $message; + } + /** * Wrapper to handle encoding and sending data to the Sentry API server. * * @param array $data Associative array of data to log */ - public function send($data) + public function send(&$data) { - if (is_callable($this->send_callback) && !call_user_func($this->send_callback, $data)) { - // if send_callback returns falsely, end native send + if (is_callable($this->send_callback) && call_user_func_array($this->send_callback, array(&$data)) === false) { + // if send_callback returns false, end native send return; } @@ -583,20 +792,15 @@ public function send($data) return; } - $message = Raven_Compat::json_encode($data); - - if (function_exists("gzcompress")) { - $message = gzcompress($message); + if ($this->transport) { + return call_user_func($this->transport, $this, $data); } - $message = base64_encode($message); // PHP's builtin curl_* function are happy without this, but the exec method requires it - $client_string = 'sentry-php/' . self::VERSION; - $timestamp = microtime(true); + $message = $this->encode($data); + $headers = array( - 'User-Agent' => $client_string, - 'X-Sentry-Auth' => $this->get_auth_header( - $timestamp, $client_string, $this->public_key, - $this->secret_key), + 'User-Agent' => $this->getUserAgent(), + 'X-Sentry-Auth' => $this->getAuthHeader(), 'Content-Type' => 'application/octet-stream' ); @@ -679,31 +883,35 @@ private function send_http($url, $data, $headers=array()) } } - /** - * Send the cURL to Sentry asynchronously. No errors will be returned from cURL - * - * @param string $url URL of the Sentry instance to log to - * @param array $data Associative array of data to log - * @param array $headers Associative array of headers - * @return bool - */ - private function send_http_asynchronous_curl_exec($url, $data, $headers) + protected function buildCurlCommand($url, $data, $headers) { // TODO(dcramer): support ca_cert $cmd = $this->curl_path.' -X POST '; foreach ($headers as $key => $value) { - $cmd .= '-H \''. $key. ': '. $value. '\' '; + $cmd .= '-H ' . escapeshellarg($key.': '.$value). ' '; } - $cmd .= '-d \''. $data .'\' '; - $cmd .= '\''. $url .'\' '; + $cmd .= '-d ' . escapeshellarg($data) . ' '; + $cmd .= escapeshellarg($url) . ' '; $cmd .= '-m 5 '; // 5 second timeout for the whole process (connect + send) if (!$this->verify_ssl) { $cmd .= '-k '; } $cmd .= '> /dev/null 2>&1 &'; // ensure exec returns immediately while curl runs in the background - exec($cmd); + return $cmd; + } + /** + * Send the cURL to Sentry asynchronously. No errors will be returned from cURL + * + * @param string $url URL of the Sentry instance to log to + * @param array $data Associative array of data to log + * @param array $headers Associative array of headers + * @return bool + */ + private function send_http_asynchronous_curl_exec($url, $data, $headers) + { + exec($this->buildCurlCommand($url, $data, $headers)); return true; // The exec method is just fire and forget, so just assume it always works } @@ -786,6 +994,12 @@ protected function get_auth_header($timestamp, $client, $api_key, $secret_key) return sprintf('Sentry %s', implode(', ', $header)); } + public function getAuthHeader() + { + $timestamp = microtime(true); + return $this->get_auth_header($timestamp, $this->getUserAgent(), $this->public_key, $this->secret_key); + } + /** * Generate an uuid4 value * @@ -925,6 +1139,7 @@ public function registerSeverityMap($map) /** * Convenience function for setting a user's ID and Email * + * @deprecated * @param string $id User's ID * @param string|null $email User's email * @param array $data Additional user data @@ -938,14 +1153,34 @@ public function set_user_data($id, $email=null, $data=array()) $this->user_context(array_merge($user, $data)); } + public function onShutdown() + { + if (!defined('RAVEN_CLIENT_END_REACHED')) { + define('RAVEN_CLIENT_END_REACHED', true); + } + $this->sendUnsentErrors(); + if ($this->curl_method == 'async') { + $this->_curl_handler->join(); + } + } + /** * Sets user context. * * @param array $data Associative array of user data + * @param bool $merge Merge existing context with new context */ - public function user_context($data) + public function user_context($data, $merge=true) { - $this->context->user = $data; + if ($merge && $this->context->user !== null) { + // bail if data is null + if (!$data) { + return; + } + $this->context->user = array_merge($this->context->user, $data); + } else { + $this->context->user = $data; + } } /** diff --git a/raven/lib/Raven/Compat.php b/raven/lib/Raven/Compat.php old mode 100644 new mode 100755 diff --git a/raven/lib/Raven/Context.php b/raven/lib/Raven/Context.php old mode 100644 new mode 100755 diff --git a/raven/lib/Raven/CurlHandler.php b/raven/lib/Raven/CurlHandler.php old mode 100644 new mode 100755 diff --git a/raven/lib/Raven/ErrorHandler.php b/raven/lib/Raven/ErrorHandler.php old mode 100644 new mode 100755 index 79bb5f2..98223f9 --- a/raven/lib/Raven/ErrorHandler.php +++ b/raven/lib/Raven/ErrorHandler.php @@ -31,149 +31,126 @@ class Raven_ErrorHandler private $old_error_handler; private $call_existing_error_handler = false; private $reservedMemory; + /** @var Raven_Client */ + private $client; private $send_errors_last = false; - - /** - * @var array - * Error types which should be processed by the handler. - * A 'null' value implies "whatever error_reporting is at time of error". - */ - private $error_types = null; - - /** - * @deprecated - * @var array - * Error types that can be processed by the handler - */ - private $validErrorTypes = array( + private $fatal_error_types = array( E_ERROR, - E_WARNING, E_PARSE, - E_NOTICE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, - E_USER_ERROR, - E_USER_WARNING, - E_USER_NOTICE, E_STRICT, - E_RECOVERABLE_ERROR, - E_DEPRECATED, - E_USER_DEPRECATED, ); /** - * @deprecated * @var array - * The default Error types that are always processed by the handler. Can be set during construction. + * Error types which should be processed by the handler. + * A 'null' value implies "whatever error_reporting is at time of error". */ - private $defaultErrorTypes = array( - E_ERROR, - E_PARSE, - E_CORE_ERROR, - E_CORE_WARNING, - E_COMPILE_ERROR, - E_COMPILE_WARNING, - E_STRICT, - ); + private $error_types = null; - public function __construct($client, $send_errors_last = false, $default_error_types = null, - $error_types = null) + public function __construct($client, $send_errors_last = false, $error_types = null, + $__error_types = null) { - $this->client = $client; - if ($default_error_types !== null) { - $this->defaultErrorTypes = $default_error_types; + // support legacy fourth argument for error types + if ($error_types === null) { + $error_types = $__error_types; } + + $this->client = $client; $this->error_types = $error_types; - register_shutdown_function(array($this, 'detectShutdown')); + $this->fatal_error_types = array_reduce($this->fatal_error_types, array($this, 'bitwiseOr')); if ($send_errors_last) { $this->send_errors_last = true; $this->client->store_errors_for_bulk_send = true; - register_shutdown_function(array($this->client, 'sendUnsentErrors')); } } + public function bitwiseOr($a, $b) + { + return $a | $b; + } + public function handleException($e, $isError = false, $vars = null) { - $e->event_id = $this->client->getIdent($this->client->captureException($e, null, null, $vars)); + $e->event_id = $this->client->captureException($e, null, null, $vars); if (!$isError && $this->call_existing_exception_handler && $this->old_exception_handler) { call_user_func($this->old_exception_handler, $e); } } - public function handleError($code, $message, $file = '', $line = 0, $context=array()) + public function handleError($type, $message, $file = '', $line = 0, $context = array()) { + // http://php.net/set_error_handler + // The following error types cannot be handled with a user defined function: E_ERROR, + // E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and + // most of E_STRICT raised in the file where set_error_handler() is called. + if (error_reporting() !== 0) { $error_types = $this->error_types; if ($error_types === null) { $error_types = error_reporting(); } - if ($error_types & $code) { - $e = new ErrorException($message, 0, $code, $file, $line); + if ($error_types & $type) { + $e = new ErrorException($message, 0, $type, $file, $line); $this->handleException($e, true, $context); } } + if ($this->call_existing_error_handler) { if ($this->old_error_handler !== null) { - return call_user_func($this->old_error_handler, $code, $message, $file, $line, $context); + return call_user_func( + $this->old_error_handler, + $type, + $message, + $file, + $line, + $context + ); } else { return false; } } - } - - /** - * Nothing by default, use it in child classes for catching other types of errors - * Only constants from $this->validErrorTypes can be used - * - * @deprecated - * @return array - */ - - protected function getAdditionalErrorTypesToProcess() - { - return array(); - } - - /** - * @deprecated - * @return array - */ - private function getErrorTypesToProcess() - { - $additionalErrorTypes = array_intersect($this->getAdditionalErrorTypesToProcess(), $this->validErrorTypes); - // array_unique so bitwise "or" operation wouldn't fail if some error type gets repeated - return array_unique(array_merge($this->defaultErrorTypes, $additionalErrorTypes)); + return true; } public function handleFatalError() { - if (null === $lastError = error_get_last()) { - return; - } - unset($this->reservedMemory); - $errors = 0; - foreach ($this->getErrorTypesToProcess() as $errorType) { - $errors |= $errorType; + if (null === $error = error_get_last()) { + return; } - if ($lastError['type'] & $errors) { + if ($this->shouldCaptureFatalError($error['type'])) { $e = new ErrorException( - @$lastError['message'], @$lastError['type'], @$lastError['type'], - @$lastError['file'], @$lastError['line'] + @$error['message'], 0, @$error['type'], + @$error['file'], @$error['line'] ); $this->handleException($e, true); } } - public function registerExceptionHandler($call_existing_exception_handler = true) + public function shouldCaptureFatalError($type) + { + return $type & $this->fatal_error_types; + } + + /** + * Register a handler which will intercept unhnalded exceptions and report them to the + * associated Sentry client. + * + * @param bool $call_existing Call any existing exception handlers after processing + * this instance. + * @return $this + */ + public function registerExceptionHandler($call_existing = true) { $this->old_exception_handler = set_exception_handler(array($this, 'handleException')); - $this->call_existing_exception_handler = $call_existing_exception_handler; + $this->call_existing_exception_handler = $call_existing; return $this; } @@ -181,19 +158,29 @@ public function registerExceptionHandler($call_existing_exception_handler = true * Register a handler which will intercept standard PHP errors and report them to the * associated Sentry client. * - * @return array + * @param bool $call_existing Call any existing errors handlers after processing + * this instance. + * @param array $error_types All error types that should be sent. + * @return $this */ - // - public function registerErrorHandler($call_existing_error_handler = true, $error_types = null) + public function registerErrorHandler($call_existing = true, $error_types = null) { if ($error_types !== null) { $this->error_types = $error_types; } $this->old_error_handler = set_error_handler(array($this, 'handleError'), E_ALL); - $this->call_existing_error_handler = $call_existing_error_handler; + $this->call_existing_error_handler = $call_existing; return $this; } + /** + * Register a fatal error handler, which will attempt to capture errors which + * shutdown the PHP process. These are commonly things like OOM or timeouts. + * + * @param int $reservedMemorySize Number of kilobytes memory space to reserve, + * which is utilized when handling fatal errors. + * @return $this + */ public function registerShutdownFunction($reservedMemorySize = 10) { register_shutdown_function(array($this, 'handleFatalError')); @@ -201,11 +188,4 @@ public function registerShutdownFunction($reservedMemorySize = 10) $this->reservedMemory = str_repeat('x', 1024 * $reservedMemorySize); return $this; } - - public function detectShutdown() - { - if (!defined('RAVEN_CLIENT_END_REACHED')) { - define('RAVEN_CLIENT_END_REACHED', true); - } - } } diff --git a/raven/lib/Raven/Exception.php b/raven/lib/Raven/Exception.php new file mode 100755 index 0000000..a6199f4 --- /dev/null +++ b/raven/lib/Raven/Exception.php @@ -0,0 +1,4 @@ +serializeString($value); } } } diff --git a/raven/lib/Raven/SanitizeDataProcessor.php b/raven/lib/Raven/SanitizeDataProcessor.php old mode 100644 new mode 100755 index 97c7c15..72d52c5 --- a/raven/lib/Raven/SanitizeDataProcessor.php +++ b/raven/lib/Raven/SanitizeDataProcessor.php @@ -64,26 +64,51 @@ public function sanitize(&$item, $key) } } - public function sanitizeHttp(&$data) + public function sanitizeException(&$data) { - if (empty($data['request'])) { - return; + foreach ($data['exception']['values'] as &$value) { + return $this->sanitizeStacktrace($value['stacktrace']); } + } + + public function sanitizeHttp(&$data) + { $http = &$data['request']; - if (empty($http['cookies'])) { - return; + if (!empty($http['cookies'])) { + $cookies = &$http['cookies']; + if (!empty($cookies[$this->session_cookie_name])) { + $cookies[$this->session_cookie_name] = self::MASK; + } } + if (!empty($http['data']) && is_array($http['data'])) { + array_walk_recursive($http['data'], array($this, 'sanitize')); + } + } - $cookies = &$http['cookies']; - if (!empty($cookies[$this->session_cookie_name])) { - $cookies[$this->session_cookie_name] = self::MASK; + public function sanitizeStacktrace(&$data) + { + foreach ($data['frames'] as &$frame) { + if (empty($frame['vars'])) { + continue; + } + array_walk_recursive($frame['vars'], array($this, 'sanitize')); } } public function process(&$data) { - array_walk_recursive($data, array($this, 'sanitize')); - $this->sanitizeHttp($data); + if (!empty($data['exception'])) { + $this->sanitizeException($data); + } + if (!empty($data['stacktrace'])) { + $this->sanitizeStacktrace($data['stacktrace']); + } + if (!empty($data['request'])) { + $this->sanitizeHttp($data); + } + if (!empty($data['extra'])) { + array_walk_recursive($data['extra'], array($this, 'sanitize')); + } } /** diff --git a/raven/lib/Raven/Serializer.php b/raven/lib/Raven/Serializer.php old mode 100644 new mode 100755 index c60ee85..43401ef --- a/raven/lib/Raven/Serializer.php +++ b/raven/lib/Raven/Serializer.php @@ -26,24 +26,72 @@ */ class Raven_Serializer { + /* + * The default mb detect order + * + * @see http://php.net/manual/en/function.mb-detect-encoding.php + */ + const DEFAULT_MB_DETECT_ORDER = 'auto'; + + /* + * Suggested detect order for western countries + */ + const WESTERN_MB_DETECT_ORDER = 'UTF-8, ASCII, ISO-8859-1, ISO-8859-2, ISO-8859-3, ISO-8859-4, ISO-8859-5, ISO-8859-6, ISO-8859-7, ISO-8859-8, ISO-8859-9, ISO-8859-10, ISO-8859-13, ISO-8859-14, ISO-8859-15, ISO-8859-16, Windows-1251, Windows-1252, Windows-1254'; + + /** + * This is the default mb detect order for the detection of encoding + * + * @var string + */ + private $mb_detect_order= self::DEFAULT_MB_DETECT_ORDER; + + /** + * @param null|string $mb_detect_order + */ + public function __construct($mb_detect_order = null) + { + if ($mb_detect_order != null) { + $this->mb_detect_order = $mb_detect_order; + } + } /** * Serialize an object (recursively) into something safe for data * sanitization and encoding. */ public function serialize($value, $max_depth=3, $_depth=0) { - if (is_object($value) || is_resource($value)) { - return $this->serializeValue($value); - } elseif ($_depth < $max_depth && is_array($value)) { + $className = is_object($value) ? get_class($value) : null; + $toArray = is_array($value) || $className === 'stdClass'; + if ($toArray && $_depth < $max_depth) { $new = array(); foreach ($value as $k => $v) { $new[$this->serializeValue($k)] = $this->serialize($v, $max_depth, $_depth + 1); } return $new; - } else { - return $this->serializeValue($value); } + return $this->serializeValue($value); + } + + protected function serializeString($value) + { + $value = (string) $value; + if (function_exists('mb_detect_encoding') + && function_exists('mb_convert_encoding') + ) { + // we always guarantee this is coerced, even if we can't detect encoding + if ($currentEncoding = mb_detect_encoding($value, $this->mb_detect_order)) { + $value = mb_convert_encoding($value, 'UTF-8', $currentEncoding); + } else { + $value = mb_convert_encoding($value, 'UTF-8'); + } + } + + if (strlen($value) > 1024) { + $value = substr($value, 0, 1014) . ' {clipped}'; + } + + return $value; } protected function serializeValue($value) @@ -57,13 +105,28 @@ protected function serializeValue($value) } elseif (is_array($value)) { return 'Array of length ' . count($value); } else { - $value = (string) $value; + return $this->serializeString($value); + } + } - if (function_exists('mb_convert_encoding')) { - $value = mb_convert_encoding($value, 'UTF-8', 'auto'); - } - return $value; - } + /** + * @return string + */ + public function getMbDetectOrder() + { + return $this->mb_detect_order; + } + + /** + * @param string $mb_detect_order + * + * @return Raven_Serializer + */ + public function setMbDetectOrder($mb_detect_order) + { + $this->mb_detect_order = $mb_detect_order; + + return $this; } } diff --git a/raven/lib/Raven/Stacktrace.php b/raven/lib/Raven/Stacktrace.php old mode 100644 new mode 100755 index df9ecfa..c3c3398 --- a/raven/lib/Raven/Stacktrace.php +++ b/raven/lib/Raven/Stacktrace.php @@ -13,44 +13,43 @@ class Raven_Stacktrace 'require_once', ); - public static function get_stack_info($frames, $trace = false, $shiftvars = true, $errcontext = null, - $frame_var_limit = Raven_Client::MESSAGE_LIMIT, $strip_prefixes = null, - $app_path = null) + public static function get_stack_info($frames, + $trace = false, + $errcontext = null, + $frame_var_limit = Raven_Client::MESSAGE_LIMIT, + $strip_prefixes = null, + $app_path = null, + $excluded_app_paths = null, + Raven_Serializer $serializer = null, + Raven_ReprSerializer $reprSerializer = null) { + $serializer = $serializer ?: new Raven_Serializer(); + $reprSerializer = $reprSerializer ?: new Raven_ReprSerializer(); + /** - * PHP's way of storing backstacks seems bass-ackwards to me - * 'function' is not the function you're in; it's any function being - * called, so we have to shift 'function' down by 1. Ugh. + * PHP stores calls in the stacktrace, rather than executing context. Sentry + * wants to know "when Im calling this code, where am I", and PHP says "I'm + * calling this function" not "I'm in this function". Due to that, we shift + * the context for a frame up one, meaning the variables (which are the calling + * args) come from the previous frame. */ $result = array(); for ($i = 0; $i < count($frames); $i++) { - $frame = $frames[$i]; + $frame = isset($frames[$i]) ? $frames[$i] : null; $nextframe = isset($frames[$i + 1]) ? $frames[$i + 1] : null; if (!array_key_exists('file', $frame)) { - // XXX: Disable capturing of anonymous functions until we can implement a better grouping mechanism. - // In our examples these generally didn't help with debugging as the information was found elsewhere - // within the exception or the stacktrace - continue; - // if (isset($frame['args'])) { - // $args = is_string($frame['args']) ? $frame['args'] : @json_encode($frame['args']); - // } - // else { - // $args = array(); - // } - // if (!empty($nextframe['class'])) { - // $context['line'] = sprintf('%s%s%s(%s)', - // $nextframe['class'], $nextframe['type'], $nextframe['function'], - // $args); - // } - // else { - // $context['line'] = sprintf('%s(%s)', $nextframe['function'], $args); - // } - // $abs_path = ''; - // $context['prefix'] = ''; - // $context['suffix'] = ''; - // $context['filename'] = $filename = '[Anonymous function]'; - // $context['lineno'] = 0; + if (!empty($frame['class'])) { + $context['line'] = sprintf('%s%s%s', + $frame['class'], $frame['type'], $frame['function']); + } else { + $context['line'] = sprintf('%s(anonymous)', $frame['function']); + } + $abs_path = ''; + $context['prefix'] = ''; + $context['suffix'] = ''; + $context['filename'] = $filename = '[Anonymous function]'; + $context['lineno'] = 0; } else { $context = self::read_source_file($frame['file'], $frame['line']); $abs_path = $frame['file']; @@ -58,56 +57,50 @@ public static function get_stack_info($frames, $trace = false, $shiftvars = true // strip base path if present $context['filename'] = self::strip_prefixes($context['filename'], $strip_prefixes); - - $module = basename($abs_path); - if (isset($nextframe['class'])) { - $module .= ':' . $nextframe['class']; - } - - if (empty($result) && isset($errcontext)) { + if ($i === 0 && isset($errcontext)) { // If we've been given an error context that can be used as the vars for the first frame. $vars = $errcontext; } else { if ($trace) { - if ($shiftvars) { - $vars = self::get_frame_context($nextframe, $frame_var_limit); - } else { - $vars = self::get_caller_frame_context($frame); - } + $vars = self::get_frame_context($nextframe, $frame_var_limit); } else { $vars = array(); } } $data = array( - // abs_path isn't overly useful, wastes space, and exposes - // filesystem internals - // 'abs_path' => $abs_path, 'filename' => $context['filename'], 'lineno' => (int) $context['lineno'], - 'module' => $module, - 'function' => $nextframe['function'], - 'pre_context' => $context['prefix'], - 'context_line' => $context['line'], - 'post_context' => $context['suffix'], + 'function' => isset($nextframe['function']) ? $nextframe['function'] : null, + 'pre_context' => $serializer->serialize($context['prefix']), + 'context_line' => $serializer->serialize($context['line']), + 'post_context' => $serializer->serialize($context['suffix']), ); // detect in_app based on app path if ($app_path) { - $data['in_app'] = (bool)(substr($abs_path, 0, strlen($app_path)) === $app_path); + $in_app = (bool)(substr($abs_path, 0, strlen($app_path)) === $app_path); + if ($in_app && $excluded_app_paths) { + foreach ($excluded_app_paths as $path) { + if (substr($abs_path, 0, strlen($path)) === $path) { + $in_app = false; + break; + } + } + } + $data['in_app'] = $in_app; } // dont set this as an empty array as PHP will treat it as a numeric array // instead of a mapping which goes against the defined Sentry spec if (!empty($vars)) { - $serializer = new Raven_ReprSerializer(); $cleanVars = array(); foreach ($vars as $key => $value) { - $value = $serializer->serialize($value); + $value = $reprSerializer->serialize($value); if (is_string($value) || is_numeric($value)) { - $cleanVars[$key] = substr($value, 0, $frame_var_limit); + $cleanVars[(string)$key] = substr($value, 0, $frame_var_limit); } else { - $cleanVars[$key] = $value; + $cleanVars[(string)$key] = $value; } } $data['vars'] = $cleanVars; @@ -119,7 +112,7 @@ public static function get_stack_info($frames, $trace = false, $shiftvars = true return array_reverse($result); } - public static function get_caller_frame_context($frame) + public static function get_default_context($frame, $frame_arg_limit = Raven_Client::MESSAGE_LIMIT) { if (!isset($frame['args'])) { return array(); @@ -128,6 +121,9 @@ public static function get_caller_frame_context($frame) $i = 1; $args = array(); foreach ($frame['args'] as $arg) { + if (is_string($arg) || is_numeric($arg)) { + $arg = substr($arg, 0, $frame_arg_limit); + } $args['param'.$i] = $arg; $i++; } @@ -136,24 +132,23 @@ public static function get_caller_frame_context($frame) public static function get_frame_context($frame, $frame_arg_limit = Raven_Client::MESSAGE_LIMIT) { - // The reflection API seems more appropriate if we associate it with the frame - // where the function is actually called (since we're treating them as function context) - if (!isset($frame['function'])) { - return array(); - } - if (!isset($frame['args'])) { return array(); } + // The reflection API seems more appropriate if we associate it with the frame + // where the function is actually called (since we're treating them as function context) + if (!isset($frame['function'])) { + return self::get_default_context($frame, $frame_arg_limit); + } if (strpos($frame['function'], '__lambda_func') !== false) { - return array(); + return self::get_default_context($frame, $frame_arg_limit); } if (isset($frame['class']) && $frame['class'] == 'Closure') { - return array(); + return self::get_default_context($frame, $frame_arg_limit); } if (strpos($frame['function'], '{closure}') !== false) { - return array(); + return self::get_default_context($frame, $frame_arg_limit); } if (in_array($frame['function'], self::$statements)) { if (empty($frame['args'])) { @@ -161,7 +156,7 @@ public static function get_frame_context($frame, $frame_arg_limit = Raven_Client return array(); } else { // Sanitize the file path - return array($frame['args'][0]); + return array('param1' => $frame['args'][0]); } } try { @@ -177,7 +172,7 @@ public static function get_frame_context($frame, $frame_arg_limit = Raven_Client $reflection = new ReflectionFunction($frame['function']); } } catch (ReflectionException $e) { - return array(); + return self::get_default_context($frame, $frame_arg_limit); } $params = $reflection->getParameters(); @@ -195,9 +190,7 @@ public static function get_frame_context($frame, $frame_arg_limit = Raven_Client } $args[$params[$i]->name] = $arg; } else { - // TODO: Sentry thinks of these as context locals, so they must be named - // Assign the argument by number - // $args[$i] = $arg; + $args['param'.$i] = $arg; } } @@ -207,11 +200,11 @@ public static function get_frame_context($frame, $frame_arg_limit = Raven_Client private static function strip_prefixes($filename, $prefixes) { if ($prefixes === null) { - return; + return $filename; } foreach ($prefixes as $prefix) { if (substr($filename, 0, strlen($prefix)) === $prefix) { - return substr($filename, strlen($prefix) + 1); + return substr($filename, strlen($prefix)); } } return $filename; diff --git a/raven/lib/Raven/TransactionStack.php b/raven/lib/Raven/TransactionStack.php new file mode 100755 index 0000000..5d428c7 --- /dev/null +++ b/raven/lib/Raven/TransactionStack.php @@ -0,0 +1,48 @@ +stack = array(); + } + + public function clear() + { + $this->stack = array(); + } + + public function peek() + { + $len = count($this->stack); + if ($len === 0) { + return null; + } + return $this->stack[$len - 1]; + } + + public function push($context) + { + $this->stack[] = $context; + } + + public function pop($context=null) + { + if (!$context) { + return array_pop($this->stack); + } + while (!empty($this->stack)) { + if (array_pop($this->stack) === $context) { + return $context; + } + } + } +} diff --git a/raven/lib/Raven/Util.php b/raven/lib/Raven/Util.php old mode 100644 new mode 100755 diff --git a/raven/lib/Raven/data/cacert.pem b/raven/lib/Raven/data/cacert.pem old mode 100644 new mode 100755 diff --git a/raven/phpunit.xml b/raven/phpunit.xml old mode 100644 new mode 100755 diff --git a/raven/test/Raven/Tests/Breadcrumbs/MonologTest.php b/raven/test/Raven/Tests/Breadcrumbs/MonologTest.php deleted file mode 100644 index f536985..0000000 --- a/raven/test/Raven/Tests/Breadcrumbs/MonologTest.php +++ /dev/null @@ -1,68 +0,0 @@ -run(Object(Illuminate\Http\Request)) -#3 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(5053): Illuminate\Routing\Router->dispatchToRoute(Object(Illuminate\Http\Request)) -#4 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(715): Illuminate\Routing\Router->dispatch(Object(Illuminate\Http\Request)) -#5 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(696): Illuminate\Foundation\Application->dispatch(Object(Illuminate\Http\Request)) -#6 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(7825): Illuminate\Foundation\Application->handle(Object(Illuminate\Http\Request), 1, true) -#7 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(8432): Illuminate\Session\Middleware->handle(Object(Illuminate\Http\Request), 1, true) -#8 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(8379): Illuminate\Cookie\Queue->handle(Object(Illuminate\Http\Request), 1, true) -#9 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(11123): Illuminate\Cookie\Guard->handle(Object(Illuminate\Http\Request), 1, true) -#10 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(657): Stack\StackedHttpKernel->handle(Object(Illuminate\Http\Request)) -#11 /sentry-laravel/examples/laravel-4.2/public/index.php(49): Illuminate\Foundation\Application->run() -#12 /sentry-laravel/examples/laravel-4.2/server.php(19): require_once('/Users/dcramer/...') -#13 {main} -EOF; - } - - public function testSimple() - { - $client = new \Raven_Client(); - $handler = new \Raven_Breadcrumbs_MonologHandler($client); - - $logger = new Monolog\Logger('sentry'); - $logger->pushHandler($handler); - $logger->addWarning('Foo'); - - $crumbs = $client->breadcrumbs->fetch(); - $this->assertEquals(count($crumbs), 1); - $this->assertEquals($crumbs[0]['message'], 'Foo'); - $this->assertEquals($crumbs[0]['category'], 'sentry'); - $this->assertEquals($crumbs[0]['level'], 'warning'); - } - - public function testErrorInMessage() - { - $client = new \Raven_Client(); - $handler = new \Raven_Breadcrumbs_MonologHandler($client); - - $logger = new Monolog\Logger('sentry'); - $logger->pushHandler($handler); - $logger->addError($this->getSampleErrorMessage()); - - $crumbs = $client->breadcrumbs->fetch(); - $this->assertEquals(count($crumbs), 1); - $this->assertEquals($crumbs[0]['data']['type'], 'Exception'); - $this->assertEquals($crumbs[0]['data']['value'], 'An unhandled exception'); - $this->assertEquals($crumbs[0]['category'], 'sentry'); - $this->assertEquals($crumbs[0]['level'], 'error'); - } -} diff --git a/raven/test/Raven/Tests/BreadcrumbsTest.php b/raven/test/Raven/Tests/BreadcrumbsTest.php deleted file mode 100644 index 5046459..0000000 --- a/raven/test/Raven/Tests/BreadcrumbsTest.php +++ /dev/null @@ -1,38 +0,0 @@ -record(array('message' => $i)); - } - - $results = $breadcrumbs->fetch(); - - $this->assertEquals(count($results), 10); - for ($i = 1; $i <= 10; $i++) { - $this->assertEquals($results[$i - 1]['message'], $i); - } - } - - public function testJson() - { - $breadcrumbs = new Raven_Breadcrumbs(1); - $breadcrumbs->record(array('message' => 'test')); - $json = $breadcrumbs->to_json(); - - $this->assertEquals(count($json['values']), 1); - $this->assertEquals($json['values'][0]['message'], 'test'); - } -} diff --git a/raven/test/Raven/Tests/ClientTest.php b/raven/test/Raven/Tests/ClientTest.php deleted file mode 100644 index 6a8dde8..0000000 --- a/raven/test/Raven/Tests/ClientTest.php +++ /dev/null @@ -1,768 +0,0 @@ -__sent_events; - } - public function send($data) - { - if (is_callable($this->send_callback) && !call_user_func($this->send_callback, $data)) { - // if send_callback returns falsely, end native send - return; - } - $this->__sent_events[] = $data; - } - public function is_http_request() - { - return true; - } - public function get_auth_header($timestamp, $client, $api_key, $secret_key) - { - return parent::get_auth_header($timestamp, $client, $api_key, $secret_key); - } - public function get_http_data() - { - return parent::get_http_data(); - } - public function get_user_data() - { - return parent::get_user_data(); - } - - /** - * Expose the current url method to test it - * - * @return string - */ - public function test_get_current_url() - { - return $this->get_current_url(); - } -} - -class Raven_Tests_ClientTest extends PHPUnit_Framework_TestCase -{ - private function create_exception() - { - try { - throw new Exception('Foo bar'); - } catch (Exception $ex) { - return $ex; - } - } - - private function create_chained_exception() - { - try { - throw new Exception('Foo bar'); - } catch (Exception $ex) { - try { - throw new Exception('Child exc', 0, $ex); - } catch (Exception $ex2) { - return $ex2; - } - } - } - - public function testParseDsnHttp() - { - $result = Raven_Client::parseDsn('http://public:secret@example.com/1'); - - $this->assertEquals($result['project'], 1); - $this->assertEquals($result['server'], 'http://example.com/api/1/store/'); - $this->assertEquals($result['public_key'], 'public'); - $this->assertEquals($result['secret_key'], 'secret'); - } - - public function testParseDsnHttps() - { - $result = Raven_Client::parseDsn('https://public:secret@example.com/1'); - - $this->assertEquals($result['project'], 1); - $this->assertEquals($result['server'], 'https://example.com/api/1/store/'); - $this->assertEquals($result['public_key'], 'public'); - $this->assertEquals($result['secret_key'], 'secret'); - } - - public function testParseDsnPath() - { - $result = Raven_Client::parseDsn('http://public:secret@example.com/app/1'); - - $this->assertEquals($result['project'], 1); - $this->assertEquals($result['server'], 'http://example.com/app/api/1/store/'); - $this->assertEquals($result['public_key'], 'public'); - $this->assertEquals($result['secret_key'], 'secret'); - } - - public function testParseDsnPort() - { - $result = Raven_Client::parseDsn('http://public:secret@example.com:9000/app/1'); - - $this->assertEquals($result['project'], 1); - $this->assertEquals($result['server'], 'http://example.com:9000/app/api/1/store/'); - $this->assertEquals($result['public_key'], 'public'); - $this->assertEquals($result['secret_key'], 'secret'); - } - - public function testParseDsnInvalidScheme() - { - try { - Raven_Client::parseDsn('gopher://public:secret@/1'); - $this->fail(); - } catch (Exception $e) { - return; - } - } - - public function testParseDsnMissingNetloc() - { - try { - Raven_Client::parseDsn('http://public:secret@/1'); - $this->fail(); - } catch (Exception $e) { - return; - } - } - - public function testParseDsnMissingProject() - { - try { - Raven_Client::parseDsn('http://public:secret@example.com'); - $this->fail(); - } catch (Exception $e) { - return; - } - } - - /** - * @expectedException InvalidArgumentException - */ - public function testParseDsnMissingPublicKey() - { - Raven_Client::parseDsn('http://:secret@example.com/1'); - } - /** - * @expectedException InvalidArgumentException - */ - public function testParseDsnMissingSecretKey() - { - Raven_Client::parseDsn('http://public@example.com/1'); - } - - public function testDsnFirstArgument() - { - $client = new Raven_Client('http://public:secret@example.com/1'); - - $this->assertEquals($client->project, 1); - $this->assertEquals($client->server, 'http://example.com/api/1/store/'); - $this->assertEquals($client->public_key, 'public'); - $this->assertEquals($client->secret_key, 'secret'); - } - - public function testDsnFirstArgumentWithOptions() - { - $client = new Raven_Client('http://public:secret@example.com/1', array( - 'site' => 'foo', - )); - - $this->assertEquals($client->project, 1); - $this->assertEquals($client->server, 'http://example.com/api/1/store/'); - $this->assertEquals($client->public_key, 'public'); - $this->assertEquals($client->secret_key, 'secret'); - $this->assertEquals($client->site, 'foo'); - } - - public function testOptionsFirstArgument() - { - $client = new Raven_Client(array( - 'server' => 'http://example.com/api/1/store/', - 'project' => 1, - )); - - $this->assertEquals($client->server, 'http://example.com/api/1/store/'); - } - - - public function testDsnInOptionsFirstArg() - { - $client = new Raven_Client(array( - 'dsn' => 'http://public:secret@example.com/1', - )); - - $this->assertEquals($client->project, 1); - $this->assertEquals($client->server, 'http://example.com/api/1/store/'); - $this->assertEquals($client->public_key, 'public'); - $this->assertEquals($client->secret_key, 'secret'); - } - - public function testDsnInOptionsSecondArg() - { - $client = new Raven_Client(null, array( - 'dsn' => 'http://public:secret@example.com/1', - )); - - $this->assertEquals($client->project, 1); - $this->assertEquals($client->server, 'http://example.com/api/1/store/'); - $this->assertEquals($client->public_key, 'public'); - $this->assertEquals($client->secret_key, 'secret'); - } - - public function testOptionsFirstArgumentWithOptions() - { - $client = new Raven_Client(array( - 'server' => 'http://example.com/api/1/store/', - 'project' => 1, - ), array( - 'site' => 'foo', - )); - - $this->assertEquals($client->server, 'http://example.com/api/1/store/'); - $this->assertEquals($client->site, 'foo'); - } - - public function testOptionsExtraData() - { - $client = new Dummy_Raven_Client(array('extra' => array('foo' => 'bar'))); - - $client->captureMessage('Test Message %s', array('foo')); - $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); - $event = array_pop($events); - $this->assertEquals($event['extra']['foo'], 'bar'); - } - - public function testEmptyExtraData() - { - $client = new Dummy_Raven_Client(array('extra' => array())); - - $client->captureMessage('Test Message %s', array('foo')); - $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); - $event = array_pop($events); - $this->assertEquals(array_key_exists('extra', $event), false); - } - - public function testCaptureMessageDoesHandleUninterpolatedMessage() - { - $client = new Dummy_Raven_Client(); - - $client->captureMessage('Test Message %s'); - $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); - $event = array_pop($events); - $this->assertEquals($event['message'], 'Test Message %s'); - } - - public function testCaptureMessageDoesHandleInterpolatedMessage() - { - $client = new Dummy_Raven_Client(); - - $client->captureMessage('Test Message %s', array('foo')); - $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); - $event = array_pop($events); - $this->assertEquals($event['message'], 'Test Message foo'); - } - - public function testCaptureMessageSetsInterface() - { - $client = new Dummy_Raven_Client(); - - $client->captureMessage('Test Message %s', array('foo')); - $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); - $event = array_pop($events); - $this->assertEquals($event['sentry.interfaces.Message'], array( - 'message' => 'Test Message %s', - 'params' => array('foo'), - )); - } - - public function testCaptureMessageHandlesOptionsAsThirdArg() - { - $client = new Dummy_Raven_Client(); - - $client->captureMessage('Test Message %s', array('foo'), array( - 'level' => Dummy_Raven_Client::WARNING, - 'extra' => array('foo' => 'bar') - )); - $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); - $event = array_pop($events); - $this->assertEquals($event['level'], Dummy_Raven_Client::WARNING); - $this->assertEquals($event['extra']['foo'], 'bar'); - } - - public function testCaptureMessageHandlesLevelAsThirdArg() - { - $client = new Dummy_Raven_Client(); - - $client->captureMessage('Test Message %s', array('foo'), Dummy_Raven_Client::WARNING); - $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); - $event = array_pop($events); - $this->assertEquals($event['level'], Dummy_Raven_Client::WARNING); - } - - public function testCaptureExceptionSetsInterfaces() - { - # TODO: it'd be nice if we could mock the stacktrace extraction function here - $client = new Dummy_Raven_Client(); - $ex = $this->create_exception(); - $client->captureException($ex); - - $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); - $event = array_pop($events); - - $exc = $event['exception']; - $this->assertEquals(count($exc['values']), 1); - $this->assertEquals($exc['values'][0]['value'], 'Foo bar'); - $this->assertEquals($exc['values'][0]['type'], 'Exception'); - $this->assertFalse(empty($exc['values'][0]['module'])); - - $this->assertFalse(empty($exc['values'][0]['stacktrace']['frames'])); - $frames = $exc['values'][0]['stacktrace']['frames']; - $frame = $frames[count($frames) - 1]; - $this->assertTrue($frame['lineno'] > 0); - $this->assertEquals($frame['module'], 'ClientTest.php:Raven_Tests_ClientTest'); - $this->assertEquals($frame['function'], 'create_exception'); - $this->assertFalse(isset($frame['vars'])); - $this->assertEquals($frame['context_line'], ' throw new Exception(\'Foo bar\');'); - $this->assertFalse(empty($frame['pre_context'])); - $this->assertFalse(empty($frame['post_context'])); - } - - public function testCaptureExceptionChainedException() - { - if (version_compare(PHP_VERSION, '5.3.0', '<')) { - $this->markTestSkipped('PHP 5.3 required for chained exceptions.'); - } - - # TODO: it'd be nice if we could mock the stacktrace extraction function here - $client = new Dummy_Raven_Client(); - $ex = $this->create_chained_exception(); - $client->captureException($ex); - - $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); - $event = array_pop($events); - - $exc = $event['exception']; - $this->assertEquals(count($exc['values']), 2); - $this->assertEquals($exc['values'][0]['value'], 'Foo bar'); - $this->assertEquals($exc['values'][1]['value'], 'Child exc'); - } - - public function testCaptureExceptionDifferentLevelsInChainedExceptionsBug() - { - if (version_compare(PHP_VERSION, '5.3.0', '<')) { - $this->markTestSkipped('PHP 5.3 required for chained exceptions.'); - } - - $client = new Dummy_Raven_Client(); - $e1 = new ErrorException('First', 0, E_DEPRECATED); - $e2 = new ErrorException('Second', 0, E_NOTICE, __FILE__, __LINE__, $e1); - $e3 = new ErrorException('Third', 0, E_ERROR, __FILE__, __LINE__, $e2); - - $client->captureException($e1); - $client->captureException($e2); - $client->captureException($e3); - $events = $client->getSentEvents(); - - $event = array_pop($events); - $this->assertEquals($event['level'], Dummy_Raven_Client::ERROR); - - $event = array_pop($events); - $this->assertEquals($event['level'], Dummy_Raven_Client::INFO); - - $event = array_pop($events); - $this->assertEquals($event['level'], Dummy_Raven_Client::WARNING); - } - - public function testCaptureExceptionHandlesOptionsAsSecondArg() - { - $client = new Dummy_Raven_Client(); - $ex = $this->create_exception(); - $client->captureException($ex, array('culprit' => 'test')); - $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); - $event = array_pop($events); - $this->assertEquals($event['culprit'], 'test'); - } - - public function testCaptureExceptionHandlesCulpritAsSecondArg() - { - $client = new Dummy_Raven_Client(); - $ex = $this->create_exception(); - $client->captureException($ex, 'test'); - $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); - $event = array_pop($events); - $this->assertEquals($event['culprit'], 'test'); - } - - public function testCaptureExceptionHandlesExcludeOption() - { - $client = new Dummy_Raven_Client(array( - 'exclude' => array('Exception'), - )); - $ex = $this->create_exception(); - $client->captureException($ex, 'test'); - $events = $client->getSentEvents(); - $this->assertEquals(count($events), 0); - } - - public function testDoesRegisterProcessors() - { - $client = new Dummy_Raven_Client(array( - 'processors' => array('Raven_SanitizeDataProcessor'), - )); - $this->assertEquals(count($client->processors), 1); - $this->assertTrue($client->processors[0] instanceof Raven_SanitizeDataProcessor); - } - - public function testProcessDoesCallProcessors() - { - $data = array("key"=>"value"); - - $processor = $this->getMock('Processor', array('process')); - $processor->expects($this->once()) - ->method('process') - ->with($data); - - $client = new Dummy_Raven_Client(); - $client->processors[] = $processor; - $client->process($data); - } - - public function testDefaultProcessorsAreUsed() - { - $client = new Dummy_Raven_Client(); - $defaults = Dummy_Raven_Client::getDefaultProcessors(); - - $this->assertEquals(count($client->processors), count($defaults)); - } - - public function testDefaultProcessorsContainSanitizeDataProcessor() - { - $defaults = Dummy_Raven_Client::getDefaultProcessors(); - - $this->assertTrue(in_array('Raven_SanitizeDataProcessor', $defaults)); - } - - public function testGetDefaultData() - { - $client = new Dummy_Raven_Client(); - $expected = array( - 'platform' => 'php', - 'project' => $client->project, - 'server_name' => $client->name, - 'site' => $client->site, - 'logger' => $client->logger, - 'tags' => $client->tags, - 'sdk' => array( - 'name' => 'sentry-php', - 'version' => $client::VERSION, - ), - ); - $this->assertEquals($expected, $client->get_default_data()); - } - - /** - * @backupGlobals - */ - public function testGetHttpData() - { - $_SERVER = array( - 'REDIRECT_STATUS' => '200', - 'CONTENT_TYPE' => 'text/xml', - 'CONTENT_LENGTH' => '99', - 'HTTP_HOST' => 'getsentry.com', - 'HTTP_ACCEPT' => 'text/html', - 'HTTP_ACCEPT_CHARSET' => 'utf-8', - 'HTTP_COOKIE' => 'cupcake: strawberry', - 'SERVER_PORT' => '443', - 'SERVER_PROTOCOL' => 'HTTP/1.1', - 'REQUEST_METHOD' => 'PATCH', - 'QUERY_STRING' => 'q=bitch&l=en', - 'REQUEST_URI' => '/welcome/', - 'SCRIPT_NAME' => '/index.php', - ); - $_POST = array( - 'stamp' => '1c', - ); - $_COOKIE = array( - 'donut' => 'chocolat', - ); - - $expected = array( - 'request' => array( - 'method' => 'PATCH', - 'url' => 'https://getsentry.com/welcome/', - 'query_string' => 'q=bitch&l=en', - 'data' => array( - 'stamp' => '1c', - ), - 'cookies' => array( - 'donut' => 'chocolat', - ), - 'headers' => array( - 'Host' => 'getsentry.com', - 'Accept' => 'text/html', - 'Accept-Charset' => 'utf-8', - 'Cookie' => 'cupcake: strawberry', - 'Content-Type' => 'text/xml', - 'Content-Length' => '99', - ), - 'env' => array( - 'REDIRECT_STATUS' => '200', - 'SERVER_PORT' => '443', - 'SERVER_PROTOCOL' => 'HTTP/1.1', - 'REQUEST_METHOD' => 'PATCH', - 'QUERY_STRING' => 'q=bitch&l=en', - 'REQUEST_URI' => '/welcome/', - 'SCRIPT_NAME' => '/index.php', - ), - ) - ); - - $client = new Dummy_Raven_Client(); - $this->assertEquals($expected, $client->get_http_data()); - } - - public function testGetUserDataWithSetUser() - { - $client = new Dummy_Raven_Client(); - - $id = 'unique_id'; - $email = 'foo@example.com'; - - $user = array( - 'username' => 'my_user', - ); - - $client->set_user_data($id, $email, $user); - - $expected = array( - 'user' => array( - 'id' => 'unique_id', - 'username' => 'my_user', - 'email' => 'foo@example.com', - ) - ); - - $this->assertEquals($expected, $client->get_user_data()); - } - - public function testGetUserDataWithNoUser() - { - $client = new Dummy_Raven_Client(); - - $expected = array( - 'user' => array( - 'id' => session_id(), - ) - ); - $this->assertEquals($expected, $client->get_user_data()); - } - - public function testGetAuthHeader() - { - $client = new Dummy_Raven_Client(); - - $clientstring = 'sentry-php/test'; - $timestamp = '1234341324.340000'; - - $expected = "Sentry sentry_timestamp={$timestamp}, sentry_client={$clientstring}, " . - "sentry_version=" . Dummy_Raven_Client::PROTOCOL . ", " . - "sentry_key=publickey, sentry_secret=secretkey"; - - $this->assertEquals($expected, $client->get_auth_header($timestamp, 'sentry-php/test', 'publickey', 'secretkey')); - } - - public function testCaptureMessageWithUserContext() - { - $client = new Dummy_Raven_Client(); - - $client->user_context(array('email' => 'foo@example.com')); - - $client->captureMessage('test'); - $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - $event = array_pop($events); - $this->assertEquals(array( - 'email' => 'foo@example.com', - ), $event['user']); - } - - public function testCaptureMessageWithTagsContext() - { - $client = new Dummy_Raven_Client(); - - $client->tags_context(array('foo' => 'bar')); - $client->tags_context(array('biz' => 'boz')); - $client->tags_context(array('biz' => 'baz')); - - $client->captureMessage('test'); - $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - $event = array_pop($events); - $this->assertEquals(array( - 'foo' => 'bar', - 'biz' => 'baz', - ), $event['tags']); - } - - public function testCaptureMessageWithExtraContext() - { - $client = new Dummy_Raven_Client(); - - $client->extra_context(array('foo' => 'bar')); - $client->extra_context(array('biz' => 'boz')); - $client->extra_context(array('biz' => 'baz')); - - $client->captureMessage('test'); - $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - $event = array_pop($events); - $this->assertEquals(array( - 'foo' => 'bar', - 'biz' => 'baz', - ), $event['extra']); - } - - public function testGetLastEventID() - { - $client = new Dummy_Raven_Client(); - $client->capture(array('message' => 'test', 'event_id' => 'abc')); - $this->assertEquals($client->getLastEventID(), 'abc'); - } - - public function cb1($data) - { - $this->assertEquals('test', $data['message']); - return false; - } - - public function cb2($data) - { - $this->assertEquals('test', $data['message']); - return true; - } - - public function testSendCallback() - { - $client = new Dummy_Raven_Client(array('send_callback' => array($this, 'cb1'))); - $client->captureMessage('test'); - $events = $client->getSentEvents(); - $this->assertEquals(0, count($events)); - - $client = new Dummy_Raven_Client(array('send_callback' => array($this, 'cb2'))); - $client->captureMessage('test'); - $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - } - - /** - * Set the server array to the test values, check the current url - * - * @dataProvider currentUrlProvider - * @param array $serverData - * @param array $options - * @param string $expected - the url expected - * @param string $message - fail message - */ - public function testCurrentUrl($serverVars, $options, $expected, $message) - { - $_SERVER = $serverVars; - - $client = new Dummy_Raven_Client($options); - $result = $client->test_get_current_url(); - - $this->assertSame($expected, $result, $message); - } - - /** - * Arrays of: - * $_SERVER data - * config - * expected url - * Fail message - * - * @return array - */ - public function currentUrlProvider() - { - return array( - array( - array(), - array(), - null, - 'No url expected for empty REQUEST_URI' - ), - array( - array( - 'REQUEST_URI' => '/', - 'HTTP_HOST' => 'example.com', - ), - array(), - 'http://example.com/', - 'The url is expected to be http with the request uri' - ), - array( - array( - 'REQUEST_URI' => '/', - 'HTTP_HOST' => 'example.com', - 'HTTPS' => 'on' - ), - array(), - 'https://example.com/', - 'The url is expected to be https because of HTTPS on' - ), - array( - array( - 'REQUEST_URI' => '/', - 'HTTP_HOST' => 'example.com', - 'SERVER_PORT' => '443' - ), - array(), - 'https://example.com/', - 'The url is expected to be https because of the server port' - ), - array( - array( - 'REQUEST_URI' => '/', - 'HTTP_HOST' => 'example.com', - 'X-FORWARDED-PROTO' => 'https' - ), - array(), - 'http://example.com/', - 'The url is expected to be http because the X-Forwarded header is ignored' - ), - array( - array( - 'REQUEST_URI' => '/', - 'HTTP_HOST' => 'example.com', - 'X-FORWARDED-PROTO' => 'https' - ), - array('trust_x_forwarded_proto' => true), - 'https://example.com/', - 'The url is expected to be https because the X-Forwarded header is trusted' - ) - ); - } -} diff --git a/raven/test/Raven/Tests/CompatTest.php b/raven/test/Raven/Tests/CompatTest.php deleted file mode 100644 index 93f7c41..0000000 --- a/raven/test/Raven/Tests/CompatTest.php +++ /dev/null @@ -1,46 +0,0 @@ -assertEquals(Raven_Compat::gethostname(), Raven_Compat::_gethostname()); - $this->assertTrue(strlen(Raven_Compat::_gethostname()) > 0); - } - - public function test_hash_hmac() - { - $result = Raven_Compat::hash_hmac('sha1', 'foo', 'bar'); - $this->assertEquals('85d155c55ed286a300bd1cf124de08d87e914f3a', $result); - - $result = Raven_Compat::_hash_hmac('sha1', 'foo', 'bar'); - $this->assertEquals('85d155c55ed286a300bd1cf124de08d87e914f3a', $result); - } - - public function test_json_encode() - { - $result = Raven_Compat::json_encode(array('foo' => array('bar' => 1))); - $this->assertEquals('{"foo":{"bar":1}}', $result); - - $result = Raven_Compat::_json_encode(array('foo' => array('bar' => 1))); - $this->assertEquals('{"foo":{"bar":1}}', $result); - - $result = Raven_Compat::_json_encode(array(1, 2, 3, 4, 'foo', 'bar')); - $this->assertEquals('[1,2,3,4,"foo","bar"]', $result); - - $result = Raven_Compat::_json_encode(array(1, 'foo', 'foobar' => 'bar')); - $this->assertEquals('{0:1,1:"foo","foobar":"bar"}', $result); - - $result = Raven_Compat::_json_encode(array(array())); - $this->assertEquals('[[]]', $result); - } -} diff --git a/raven/test/Raven/Tests/ErrorHandlerTest.php b/raven/test/Raven/Tests/ErrorHandlerTest.php deleted file mode 100644 index fb6b224..0000000 --- a/raven/test/Raven/Tests/ErrorHandlerTest.php +++ /dev/null @@ -1,147 +0,0 @@ -errorLevel = error_reporting(); - $this->errorHandlerCalled = false; - $this->existingErrorHandler = set_error_handler(array($this, 'errorHandler'), -1); - } - - public function errorHandler() - { - $this->errorHandlerCalled = true; - } - - public function tearDown() - { - // XXX(dcramer): this isn't great as it doesnt restore the old error reporting level - set_error_handler(array($this, 'errorHandler'), error_reporting()); - error_reporting($this->errorLevel); - } - - public function testErrorsAreLoggedAsExceptions() - { - $client = $this->getMock('Client', array('captureException', 'getIdent', 'sendUnsentErrors')); - $client->expects($this->once()) - ->method('captureException') - ->with($this->isInstanceOf('ErrorException')); - - $handler = new Raven_ErrorHandler($client, E_ALL); - $handler->handleError(E_WARNING, 'message'); - } - - public function testExceptionsAreLogged() - { - $client = $this->getMock('Client', array('captureException', 'getIdent')); - $client->expects($this->once()) - ->method('captureException') - ->with($this->isInstanceOf('ErrorException')); - - $e = new ErrorException('message', 0, E_WARNING, '', 0); - - $handler = new Raven_ErrorHandler($client); - $handler->handleException($e); - } - - public function testErrorHandlerPassErrorReportingPass() - { - $client = $this->getMock('Client', array('captureException', 'getIdent')); - $client->expects($this->once()) - ->method('captureException'); - - $handler = new Raven_ErrorHandler($client); - $handler->registerErrorHandler(false, -1); - - error_reporting(E_USER_WARNING); - trigger_error('Warning', E_USER_WARNING); - } - - public function testErrorHandlerPropagates() - { - $client = $this->getMock('Client', array('captureException', 'getIdent')); - $client->expects($this->never()) - ->method('captureException'); - - $handler = new Raven_ErrorHandler($client); - $handler->registerErrorHandler(true, E_DEPRECATED); - - error_reporting(E_USER_WARNING); - trigger_error('Warning', E_USER_WARNING); - - $this->assertEquals($this->errorHandlerCalled, 1); - } - - public function testErrorHandlerRespectsErrorReportingDefault() - { - $client = $this->getMock('Client', array('captureException', 'getIdent')); - $client->expects($this->once()) - ->method('captureException'); - - error_reporting(E_DEPRECATED); - - $handler = new Raven_ErrorHandler($client); - $handler->registerErrorHandler(true); - - error_reporting(E_ALL); - trigger_error('Warning', E_USER_WARNING); - - $this->assertEquals($this->errorHandlerCalled, 1); - } - - // Because we cannot **know** that a user silenced an error, we always - // defer to respecting the error reporting settings. - public function testSilentErrorsAreReported() - { - $client = $this->getMock('Client', array('captureException', 'getIdent')); - $client->expects($this->never()) - ->method('captureException'); - - error_reporting(E_USER_WARNING); - - $handler = new Raven_ErrorHandler($client); - $handler->registerErrorHandler(false); - - @trigger_error('Silent', E_USER_WARNING); - } - - public function testErrorHandlerDefaultsErrorReporting() - { - $client = $this->getMock('Client', array('captureException', 'getIdent')); - $client->expects($this->never()) - ->method('captureException'); - - error_reporting(E_USER_ERROR); - - $handler = new Raven_ErrorHandler($client); - $handler->registerErrorHandler(false); - - trigger_error('Warning', E_USER_WARNING); - } - - public function testFluidInterface() - { - $client = $this->getMock('Client', array('captureException', 'getIdent')); - $handler = new Raven_ErrorHandler($client); - $result = $handler->registerErrorHandler(); - $this->assertEquals($result, $handler); - $result = $handler->registerExceptionHandler(); - $this->assertEquals($result, $handler); - // TODO(dcramer): cant find a great way to test resetting the shutdown - // handler - // $result = $handler->registerShutdownHandler(); - // $this->assertEquals($result, $handler); - } -} diff --git a/raven/test/Raven/Tests/ReprSerializerTest.php b/raven/test/Raven/Tests/ReprSerializerTest.php deleted file mode 100644 index dc8ed52..0000000 --- a/raven/test/Raven/Tests/ReprSerializerTest.php +++ /dev/null @@ -1,79 +0,0 @@ -serialize($input); - $this->assertEquals(array('1', '2', '3'), $result); - } - - public function testObjectsAreStrings() - { - $serializer = new Raven_ReprSerializer(); - $input = new Raven_StacktraceTestObject(); - $result = $serializer->serialize($input); - $this->assertEquals('Object Raven_StacktraceTestObject', $result); - } - - public function testIntsAreInts() - { - $serializer = new Raven_ReprSerializer(); - $input = 1; - $result = $serializer->serialize($input); - $this->assertTrue(is_string($result)); - $this->assertEquals(1, $result); - } - - public function testFloats() - { - $serializer = new Raven_ReprSerializer(); - $input = 1.5; - $result = $serializer->serialize($input); - $this->assertTrue(is_string($result)); - $this->assertEquals('1.5', $result); - } - - public function testBooleans() - { - $serializer = new Raven_ReprSerializer(); - $input = true; - $result = $serializer->serialize($input); - $this->assertTrue(is_string($result)); - $this->assertEquals('true', $result); - - $input = false; - $result = $serializer->serialize($input); - $this->assertTrue(is_string($result)); - $this->assertEquals('false', $result); - } - - public function testNull() - { - $serializer = new Raven_ReprSerializer(); - $input = null; - $result = $serializer->serialize($input); - $this->assertTrue(is_string($result)); - $this->assertEquals('null', $result); - } - - public function testRecursionMaxDepth() - { - $serializer = new Raven_ReprSerializer(); - $input = array(); - $input[] = &$input; - $result = $serializer->serialize($input, 3); - $this->assertEquals(array(array(array('Array of length 1'))), $result); - } -} diff --git a/raven/test/Raven/Tests/SanitizeDataProcessorTest.php b/raven/test/Raven/Tests/SanitizeDataProcessorTest.php deleted file mode 100644 index 40c6aa6..0000000 --- a/raven/test/Raven/Tests/SanitizeDataProcessorTest.php +++ /dev/null @@ -1,198 +0,0 @@ - array( - 'data' => array( - 'foo' => 'bar', - 'password' => 'hello', - 'the_secret' => 'hello', - 'a_password_here' => 'hello', - 'mypasswd' => 'hello', - 'authorization' => 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', - 'card_number' => array( - '1111', - '2222', - '3333', - '4444' - ) - ), - ) - ); - - $client = new Raven_Client(); - $processor = new Raven_SanitizeDataProcessor($client); - $processor->process($data); - - $vars = $data['request']['data']; - $this->assertEquals($vars['foo'], 'bar'); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['password']); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['the_secret']); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['a_password_here']); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['mypasswd']); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['authorization']); - - $this->markTestIncomplete('Array scrubbing has not been implemented yet.'); - - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['card_number']['0']); - } - - public function testDoesFilterSessionId() - { - $data = array( - 'request' => array( - 'cookies' => array( - ini_get('session.name') => 'abc', - ), - ) - ); - - $client = new Raven_Client(); - $processor = new Raven_SanitizeDataProcessor($client); - $processor->process($data); - - $cookies = $data['request']['cookies']; - $this->assertEquals($cookies[ini_get('session.name')], Raven_SanitizeDataProcessor::MASK); - } - - public function testDoesFilterCreditCard() - { - $data = array( - 'ccnumba' => '4242424242424242' - ); - - $client = new Raven_Client(); - $processor = new Raven_SanitizeDataProcessor($client); - $processor->process($data); - - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $data['ccnumba']); - } - - /** - * @covers setProcessorOptions - * - */ - public function testSettingProcessorOptions() - { - $client = new Raven_Client(); - $processor = new Raven_SanitizeDataProcessor($client); - - $this->assertEquals($processor->getFieldsRe(), '/(authorization|password|passwd|secret|password_confirmation|card_number|auth_pw)/i', 'got default fields'); - $this->assertEquals($processor->getValuesRe(), '/^(?:\d[ -]*?){13,16}$/', 'got default values'); - - $options = array( - 'fields_re' => '/(api_token)/i', - 'values_re' => '/^(?:\d[ -]*?){15,16}$/' - ); - - $processor->setProcessorOptions($options); - - $this->assertEquals($processor->getFieldsRe(), '/(api_token)/i', 'overwrote fields'); - $this->assertEquals($processor->getValuesRe(), '/^(?:\d[ -]*?){15,16}$/', 'overwrote values'); - } - - /** - * @dataProvider overrideDataProvider - * - * @param $processorOptions - * @param $client_options - * @param $dsn - */ - public function testOverrideOptions($processorOptions, $client_options, $dsn) - { - $client = new Raven_Client($dsn, $client_options); - $processor = $client->processors[0]; - - $this->assertInstanceOf('Raven_SanitizeDataProcessor', $processor); - $this->assertEquals($processor->getFieldsRe(), $processorOptions['Raven_SanitizeDataProcessor']['fields_re'], 'overwrote fields'); - $this->assertEquals($processor->getValuesRe(), $processorOptions['Raven_SanitizeDataProcessor']['values_re'], 'overwrote values'); - } - - /** - * @depends testOverrideOptions - * @dataProvider overrideDataProvider - * - * @param $processorOptions - * @param $client_options - * @param $dsn - */ - public function testOverridenSanitize($processorOptions, $client_options, $dsn) - { - $data = array( - 'request' => array( - 'data' => array( - 'foo' => 'bar', - 'password' => 'hello', - 'the_secret' => 'hello', - 'a_password_here' => 'hello', - 'mypasswd' => 'hello', - 'api_token' => 'nioenio3nrio3jfny89nby9bhr#RML#R', - 'authorization' => 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', - 'card_number' => array( - '1111111111111111', - '2222', - ) - ), - ) - ); - - $client = new Raven_Client($dsn, $client_options); - $processor = $client->processors[0]; - - $this->assertInstanceOf('Raven_SanitizeDataProcessor', $processor); - $this->assertEquals($processor->getFieldsRe(), $processorOptions['Raven_SanitizeDataProcessor']['fields_re'], 'overwrote fields'); - $this->assertEquals($processor->getValuesRe(), $processorOptions['Raven_SanitizeDataProcessor']['values_re'], 'overwrote values'); - - $processor->process($data); - - $vars = $data['request']['data']; - $this->assertEquals($vars['foo'], 'bar', 'did not alter foo'); - $this->assertEquals($vars['password'], 'hello', 'did not alter password'); - $this->assertEquals($vars['the_secret'], 'hello', 'did not alter the_secret'); - $this->assertEquals($vars['a_password_here'], 'hello', 'did not alter a_password_here'); - $this->assertEquals($vars['mypasswd'], 'hello', 'did not alter mypasswd'); - $this->assertEquals($vars['authorization'], 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', 'did not alter authorization'); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['api_token'], 'masked api_token'); - - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['card_number']['0'], 'masked card_number[0]'); - $this->assertEquals($vars['card_number']['1'], $vars['card_number']['1'], 'did not alter card_number[1]'); - } - - /** - * Provides data for testing overriding the processor options - * - * @return array - */ - public static function overrideDataProvider() - { - $processorOptions = array( - 'Raven_SanitizeDataProcessor' => array( - 'fields_re' => '/(api_token)/i', - 'values_re' => '/^(?:\d[ -]*?){15,16}$/' - ) - ); - - $client_options = array( - 'processors' => array('Raven_SanitizeDataProcessor'), - 'processorOptions' => $processorOptions - ); - - $dsn = 'http://9aaa31f9a05b4e72aaa06aa8157a827a:9aa7aa82a9694a08a1a7589a2a035a9a@sentry.domain.tld/1'; - - return array( - array($processorOptions, $client_options, $dsn) - ); - } -} diff --git a/raven/test/Raven/Tests/SerializerTest.php b/raven/test/Raven/Tests/SerializerTest.php deleted file mode 100644 index 0bc43a2..0000000 --- a/raven/test/Raven/Tests/SerializerTest.php +++ /dev/null @@ -1,84 +0,0 @@ -serialize($input); - $this->assertEquals(array('1', '2', '3'), $result); - } - - public function testObjectsAreStrings() - { - $serializer = new Raven_Serializer(); - $input = new Raven_StacktraceTestObject(); - $result = $serializer->serialize($input); - $this->assertEquals('Object Raven_StacktraceTestObject', $result); - } - - public function testIntsAreInts() - { - $serializer = new Raven_Serializer(); - $input = 1; - $result = $serializer->serialize($input); - $this->assertTrue(is_integer($result)); - $this->assertEquals(1, $result); - } - - public function testFloats() - { - $serializer = new Raven_Serializer(); - $input = 1.5; - $result = $serializer->serialize($input); - $this->assertTrue(is_float($result)); - $this->assertEquals(1.5, $result); - } - - public function testBooleans() - { - $serializer = new Raven_Serializer(); - $input = true; - $result = $serializer->serialize($input); - $this->assertTrue(is_bool($result)); - $this->assertEquals(true, $result); - - $input = false; - $result = $serializer->serialize($input); - $this->assertTrue(is_bool($result)); - $this->assertEquals(false, $result); - } - - public function testNull() - { - $serializer = new Raven_Serializer(); - $input = null; - $result = $serializer->serialize($input); - $this->assertTrue(is_null($result)); - $this->assertEquals(null, $result); - } - - public function testRecursionMaxDepth() - { - $serializer = new Raven_Serializer(); - $input = array(); - $input[] = &$input; - $result = $serializer->serialize($input, 3); - $this->assertEquals(array(array(array('Array of length 1'))), $result); - } -} diff --git a/raven/test/Raven/Tests/StacktraceTest.php b/raven/test/Raven/Tests/StacktraceTest.php deleted file mode 100644 index b9269c7..0000000 --- a/raven/test/Raven/Tests/StacktraceTest.php +++ /dev/null @@ -1,304 +0,0 @@ - 0) { - return call_user_func('raven_test_recurse', $times, $callback); - } - - return call_user_func($callback); -} - -function raven_test_create_stacktrace($args=null, $times=3) -{ - return raven_test_recurse($times, 'debug_backtrace'); -} - -class Raven_Tests_StacktraceTest extends PHPUnit_Framework_TestCase -{ - public function testCanTraceParamContext() - { - $stack = raven_test_create_stacktrace(array('biz', 'baz'), 0); - - $frame = $stack[2]; - $params = Raven_Stacktrace::get_frame_context($frame); - $this->assertEquals($params['args'], array('biz', 'baz')); - $this->assertEquals($params['times'], 0); - } - - public function testSimpleTrace() - { - $stack = array( - array( - "file" => dirname(__FILE__) . "/resources/a.php", - "line" => 11, - "function" => "a_test", - "args"=> array( - "friend", - ), - ), - array( - "file" => dirname(__FILE__) . "/resources/b.php", - "line" => 3, - "args"=> array( - "/tmp/a.php", - ), - "function" => "include_once", - ), - ); - - $frames = Raven_Stacktrace::get_stack_info($stack, true); - - $frame = $frames[0]; - $this->assertEquals('b.php', $frame["module"]); - $this->assertEquals(3, $frame["lineno"]); - $this->assertNull($frame["function"]); - $this->assertEquals("include_once '/tmp/a.php';", $frame["context_line"]); - $frame = $frames[1]; - $this->assertEquals('a.php', $frame["module"]); - $this->assertEquals(11, $frame["lineno"]); - $this->assertEquals('include_once', $frame["function"]); - $this->assertEquals('a_test($foo);', $frame["context_line"]); - } - - public function testSimpleUnshiftedTrace() - { - $stack = array( - array( - "file" => dirname(__FILE__) . "/resources/a.php", - "line" => 11, - "function" => "a_test", - "args"=> array( - "friend", - ), - ), - array( - "file" => dirname(__FILE__) . "/resources/b.php", - "line" => 3, - "args"=> array( - "/tmp/a.php", - ), - "function" => "include_once", - ), - ); - - $frames = Raven_Stacktrace::get_stack_info($stack, true, false); - - $frame = $frames[0]; - $this->assertEquals('b.php', $frame["module"]); - $this->assertEquals(3, $frame["lineno"]); - $this->assertNull($frame["function"]); - $this->assertEquals('/tmp/a.php', $frame['vars']['param1']); - $this->assertEquals("include_once '/tmp/a.php';", $frame["context_line"]); - $frame = $frames[1]; - $this->assertEquals('a.php', $frame["module"]); - $this->assertEquals(11, $frame["lineno"]); - $this->assertEquals('include_once', $frame["function"]); - $this->assertEquals('friend', $frame['vars']['param1']); - $this->assertEquals('a_test($foo);', $frame["context_line"]); - } - - public function testShiftedCaptureVars() - { - $stack = array( - array( - "file" => dirname(__FILE__) . "/resources/a.php", - "line" => 11, - "function" => "a_test", - "args"=> array( - "friend", - ), - ), - array( - "file" => dirname(__FILE__) . "/resources/b.php", - "line" => 3, - "args"=> array( - "/tmp/a.php", - ), - "function" => "include_once", - ), - ); - - $vars = array( - "foo" => "bar", - "baz" => "zoom" - ); - - $frames = Raven_Stacktrace::get_stack_info($stack, true, true, $vars); - - $frame = $frames[0]; - $this->assertEquals('b.php', $frame["module"]); - $this->assertEquals(3, $frame["lineno"]); - $this->assertNull($frame["function"]); - $this->assertEquals("include_once '/tmp/a.php';", $frame["context_line"]); - $this->assertFalse(isset($frame['vars'])); - $frame = $frames[1]; - $this->assertEquals('a.php', $frame["module"]); - $this->assertEquals(11, $frame["lineno"]); - $this->assertEquals('include_once', $frame["function"]); - $this->assertEquals('a_test($foo);', $frame["context_line"]); - $this->assertEquals($vars, $frame['vars']); - } - - public function testDoesNotModifyCaptureVars() - { - $stack = array( - array( - "file" => dirname(__FILE__) . "/resources/a.php", - "line" => 11, - "function" => "a_test", - "args"=> array( - "friend", - ), - ), - array( - "file" => dirname(__FILE__) . "/resources/b.php", - "line" => 3, - "args"=> array( - "/tmp/a.php", - ), - "function" => "include_once", - ), - ); - - // PHP's errcontext as passed to the error handler contains REFERENCES to any vars that were in the global scope. - // Modification of these would be really bad, since if control is returned (non-fatal error) we'll have altered the state of things! - $originalFoo = "bloopblarp"; - $iAmFoo = $originalFoo; - $vars = array( - "foo" => &$iAmFoo - ); - - $frames = Raven_Stacktrace::get_stack_info($stack, true, true, $vars, 5); - - // Check we haven't modified our vars. - $this->assertEquals($originalFoo, $vars["foo"]); - - $frame = $frames[1]; - // Check that we did truncate the variable in our output - $this->assertEquals(5, strlen($frame['vars']['foo'])); - } - - public function testUnshiftedCaptureVars() - { - $stack = array( - array( - "file" => dirname(__FILE__) . "/resources/a.php", - "line" => 11, - "function" => "a_test", - "args"=> array( - "friend", - ), - ), - array( - "file" => dirname(__FILE__) . "/resources/b.php", - "line" => 3, - "args"=> array( - "/tmp/a.php", - ), - "function" => "include_once", - ), - ); - - $vars = array( - "foo" => "bar", - "baz" => "zoom" - ); - - $frames = Raven_Stacktrace::get_stack_info($stack, true, false, $vars); - - $frame = $frames[0]; - $this->assertEquals('b.php', $frame["module"]); - $this->assertEquals(3, $frame["lineno"]); - $this->assertNull($frame["function"]); - $this->assertEquals(array('param1' => '/tmp/a.php'), $frame['vars']); - $this->assertEquals("include_once '/tmp/a.php';", $frame["context_line"]); - $frame = $frames[1]; - $this->assertEquals('a.php', $frame["module"]); - $this->assertEquals(11, $frame["lineno"]); - $this->assertEquals('include_once', $frame["function"]); - $this->assertEquals($vars, $frame['vars']); - $this->assertEquals('a_test($foo);', $frame["context_line"]); - } - - public function testDoesFixFrameInfo() - { - /** - * PHP's way of storing backstacks seems bass-ackwards to me - * 'function' is not the function you're in; it's any function being - * called, so we have to shift 'function' down by 1. Ugh. - */ - $stack = raven_test_create_stacktrace(); - - $frames = Raven_Stacktrace::get_stack_info($stack, true); - // just grab the last few frames - $frames = array_slice($frames, -5); - $frame = $frames[0]; - $this->assertEquals('StacktraceTest.php:Raven_Tests_StacktraceTest', $frame['module']); - $this->assertEquals('testDoesFixFrameInfo', $frame['function']); - $frame = $frames[1]; - $this->assertEquals('StacktraceTest.php', $frame['module']); - $this->assertEquals('raven_test_create_stacktrace', $frame['function']); - $frame = $frames[2]; - $this->assertEquals('StacktraceTest.php', $frame['module']); - $this->assertEquals('raven_test_recurse', $frame['function']); - $frame = $frames[3]; - $this->assertEquals('StacktraceTest.php', $frame['module']); - $this->assertEquals('raven_test_recurse', $frame['function']); - $frame = $frames[4]; - $this->assertEquals('StacktraceTest.php', $frame['module']); - $this->assertEquals('raven_test_recurse', $frame['function']); - } - - public function testInApp() - { - $stack = array( - array( - "file" => dirname(__FILE__) . "/resources/a.php", - "line" => 11, - "function" => "a_test", - ), - array( - "file" => dirname(__FILE__) . "/resources/b.php", - "line" => 3, - "function" => "include_once", - ), - ); - - $frames = Raven_Stacktrace::get_stack_info($stack, true, null, null, 0, null, dirname(__FILE__)); - - $this->assertEquals($frames[0]['in_app'], true); - $this->assertEquals($frames[1]['in_app'], true); - } - - public function testBasePath() - { - $stack = array( - array( - "file" => dirname(__FILE__) . "/resources/a.php", - "line" => 11, - "function" => "a_test", - ), - array( - "file" => dirname(__FILE__) . "/resources/b.php", - "line" => 3, - "function" => "include_once", - ), - ); - - $frames = Raven_Stacktrace::get_stack_info($stack, true, null, null, 0, array(dirname(__FILE__))); - - $this->assertEquals($frames[0]['filename'], 'resources/b.php'); - $this->assertEquals($frames[1]['filename'], 'resources/a.php'); - } -} diff --git a/raven/test/Raven/Tests/UtilTest.php b/raven/test/Raven/Tests/UtilTest.php deleted file mode 100644 index 856b770..0000000 --- a/raven/test/Raven/Tests/UtilTest.php +++ /dev/null @@ -1,32 +0,0 @@ - 'bar'); - $result = Raven_Util::get($input, 'baz', 'foo'); - $this->assertEquals('foo', $result); - } - - public function testGetReturnsPresentValuesEvenWhenEmpty() - { - $input = array('foo' => ''); - $result = Raven_Util::get($input, 'foo', 'bar'); - $this->assertEquals('', $result); - } -} diff --git a/raven/test/Raven/Tests/resources/a.php b/raven/test/Raven/Tests/resources/a.php deleted file mode 100644 index 78ae29c..0000000 --- a/raven/test/Raven/Tests/resources/a.php +++ /dev/null @@ -1,11 +0,0 @@ -