Add Raven client for Sentry

This commit is contained in:
Raphaël Doursenaud 2015-08-06 18:25:51 +02:00
parent 1ffb28a460
commit e5eefa685f
37 changed files with 8946 additions and 2 deletions

View File

@ -18,7 +18,8 @@
"mobiledetect/mobiledetectlib": "2.8.3",
"phpoffice/phpexcel": "1.8.0",
"restler/framework": "^3.0",
"tecnick.com/tcpdf": "6.2.6"
"tecnick.com/tcpdf": "6.2.6",
"raven/raven": "^0.12.0"
},
"suggest": {
"ext-mysqlnd": "To use with MySQL or MariaDB",

56
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "ca5a74259e0d1773089838908d58cb31",
"hash": "32e1fa78cc95c32b154a38e07706874c",
"packages": [
{
"name": "ccampbell/chromephp",
@ -199,6 +199,60 @@
],
"time": "2014-03-02 15:22:49"
},
{
"name": "raven/raven",
"version": "0.12.0",
"source": {
"type": "git",
"url": "https://github.com/getsentry/raven-php.git",
"reference": "bd247ca2a8fd9ccfb99b60285c9b31286384a92b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/getsentry/raven-php/zipball/bd247ca2a8fd9ccfb99b60285c9b31286384a92b",
"reference": "bd247ca2a8fd9ccfb99b60285c9b31286384a92b",
"shasum": ""
},
"require": {
"ext-curl": "*",
"php": ">=5.2.4"
},
"require-dev": {
"fabpot/php-cs-fixer": "^1.8.0",
"phpunit/phpunit": "^4.6.6"
},
"bin": [
"bin/raven"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.12.x-dev"
}
},
"autoload": {
"psr-0": {
"Raven_": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD"
],
"authors": [
{
"name": "David Cramer",
"email": "dcramer@gmail.com"
}
],
"description": "A PHP client for Sentry (http://getsentry.com)",
"homepage": "http://getsentry.com",
"keywords": [
"log",
"logging"
],
"time": "2015-05-19 20:20:08"
},
{
"name": "restler/framework",
"version": "3.0.0",

View File

@ -6,6 +6,7 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname(dirname($vendorDir));
return array(
'Raven_' => array($vendorDir . '/raven/raven/lib'),
'PHPExcel' => array($vendorDir . '/phpoffice/phpexcel/Classes'),
'Luracast\\Restler' => array($vendorDir . '/restler/framework'),
'Detection' => array($vendorDir . '/mobiledetect/mobiledetectlib/namespaced'),

View File

@ -340,5 +340,61 @@
"pdf417",
"qrcode"
]
},
{
"name": "raven/raven",
"version": "0.12.0",
"version_normalized": "0.12.0.0",
"source": {
"type": "git",
"url": "https://github.com/getsentry/raven-php.git",
"reference": "bd247ca2a8fd9ccfb99b60285c9b31286384a92b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/getsentry/raven-php/zipball/bd247ca2a8fd9ccfb99b60285c9b31286384a92b",
"reference": "bd247ca2a8fd9ccfb99b60285c9b31286384a92b",
"shasum": ""
},
"require": {
"ext-curl": "*",
"php": ">=5.2.4"
},
"require-dev": {
"fabpot/php-cs-fixer": "^1.8.0",
"phpunit/phpunit": "^4.6.6"
},
"time": "2015-05-19 20:20:08",
"bin": [
"bin/raven"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.12.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-0": {
"Raven_": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD"
],
"authors": [
{
"name": "David Cramer",
"email": "dcramer@gmail.com"
}
],
"description": "A PHP client for Sentry (http://getsentry.com)",
"homepage": "http://getsentry.com",
"keywords": [
"log",
"logging"
]
}
]

View File

@ -0,0 +1,5 @@
*.lock
package.xml
/vendor
.idea
.php_cs.cache

View File

@ -0,0 +1,12 @@
<?php
$finder = Symfony\CS\Finder\DefaultFinder::create()
->in(__DIR__)
;
return Symfony\CS\Config\Config::create()
->setUsingCache(true)
->setUsingLinter(true)
->level(Symfony\CS\FixerInterface::PSR2_LEVEL)
->finder($finder)
;

View File

@ -0,0 +1,30 @@
language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
matrix:
allow_failures:
- php: hhvm
- php: 7.0
fast_finish: true
sudo: false
cache:
directories:
- $HOME/.composer/cache
before_install:
- 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

View File

@ -0,0 +1,4 @@
The Raven PHP client was originally written by Michael van Tellingen
and is maintained by the Sentry Team.
http://github.com/getsentry/raven-php/contributors

View File

@ -0,0 +1,39 @@
0.12.0
------
- Bumped protocol version to 6.
- Fixed an issue with the async curl handler (GH-216).
- Removed UDP transport.
0.11.0
------
- New configuration parameter: 'release'
- New configuration parameter: 'message_limit'
- New configuration parameter: 'curl_ssl_version'
- New configuration parameter: 'curl_ipv4'
- New configuration parameter: 'verify_ssl'
- Updated remote endpoint to use modern project-based path.
- Expanded default sanitizer support to include 'auth_pw' attribute.
0.10.0
------
- Added a default certificate bundle which includes common root CA's
as well as getsentry.com's CA.
0.9.1
-----
- Change default curl connection to 'sync'
- Improve CLI reporting
0.9.0
-----
- Protocol version 5
- Default to asynchronous HTTP handler using curl_multi.
(For previous versions see the commit history)

View File

@ -0,0 +1,12 @@
Copyright (c) 2012 Sentry Team and individual contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the Raven nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,17 @@
.PHONY: test
develop:
composer install --dev
make setup-git
cs:
vendor/bin/php-cs-fixer fix --config-file=.php_cs --verbose --diff
cs-dry-run:
vendor/bin/php-cs-fixer fix --config-file=.php_cs --verbose --diff --dry-run
test:
vendor/bin/phpunit
setup-git:
git config branch.autosetuprebase always

View File

@ -0,0 +1,273 @@
raven-php
=========
.. image:: https://secure.travis-ci.org/getsentry/raven-php.png?branch=master
:target: http://travis-ci.org/getsentry/raven-php
raven-php is a PHP client for `Sentry <http://aboutsentry.com/>`_.
.. 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());
}
// 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()
),
)));
// 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 <https://getcomposer.org/>`_ to manage
dependencies, you can add Raven with it.
::
$ composer require raven/raven:$VERSION
(replace ``$VERSION`` with one of the available versions on `Packagist <https://packagist.org/packages/raven/raven>`_)
or to get the latest version off the master branch:
::
$ composer require raven/raven: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 raven/raven:^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/raven-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/raven test https://public:secret@app.getsentry.com/1
Client configuration:
-> servers: [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();
Contributing
------------
First, make sure you can run the test suite. Install development dependencies :
::
$ composer install
You may now use phpunit :
::
$ vendor/bin/phpunit
Resources
---------
* `Bug Tracker <http://github.com/getsentry/raven-php/issues>`_
* `Code <http://github.com/getsentry/raven-php>`_
* `Mailing List <https://groups.google.com/group/getsentry>`_
* `IRC <irc://irc.freenode.net/sentry>`_ (irc.freenode.net, #sentry)

View File

@ -0,0 +1,88 @@
#!/usr/bin/php
<?php
// Maximize error reporting
error_reporting(E_ALL | E_STRICT);
// TODO: if we could get rid of this and have composer figure things out it'd make it
// a bit more sane
require(dirname(__file__) . '/../lib/Raven/Autoloader.php');
Raven_Autoloader::register();
function raven_cli_test($command, $args)
{
// Do something silly
try {
throw new Exception('This is a test exception sent from the Raven CLI.');
} catch (Exception $ex) {
return $ex;
}
}
function cmd_test($dsn)
{
if (empty($dsn)) {
exit('ERROR: Missing DSN value');
}
// Parse DSN as a test
try {
$parsed = Raven_Client::parseDSN($dsn);
} catch (InvalidArgumentException $ex) {
exit("ERROR: There was an error parsing your DSN:\n " . $ex->getMessage());
}
$client = new Raven_Client($dsn, array(
'trace' => true,
'curl_method' => 'sync',
));
$config = get_object_vars($client);
$required_keys = array('servers', 'project', 'public_key', 'secret_key');
echo "Client configuration:\n";
foreach ($required_keys as $key) {
if (empty($config[$key])) {
exit("ERROR: Missing configuration for $key");
}
if (is_array($config[$key])) {
echo "-> $key: [".implode(", ", $config[$key])."]\n";
} else {
echo "-> $key: $config[$key]\n";
}
}
echo "\n";
echo "Sending a test event:\n";
$ex = raven_cli_test("command name", array("foo" => "bar"));
$event_id = $client->captureException($ex);
echo "-> event ID: $event_id\n";
$last_error = $client->getLastError();
if (!empty($last_error)) {
exit("ERROR: There was an error sending the test event:\n " . $last_error);
}
echo "\n";
echo "Done!";
}
function main() {
global $argv;
$cmd = $argv[1];
switch ($cmd) {
case 'test':
cmd_test(@$argv[2]);
break;
default:
exit('Usage: raven test <dsn>');
}
}
main();

View File

@ -0,0 +1,35 @@
{
"name": "raven/raven",
"type": "library",
"description": "A PHP client for Sentry (http://getsentry.com)",
"keywords": ["log", "logging"],
"homepage": "http://getsentry.com",
"license": "BSD",
"authors": [
{
"name": "David Cramer",
"email": "dcramer@gmail.com"
}
],
"require-dev": {
"fabpot/php-cs-fixer": "^1.8.0",
"phpunit/phpunit": "^4.6.6"
},
"require": {
"php": ">=5.2.4",
"ext-curl": "*"
},
"bin": [
"bin/raven"
],
"autoload": {
"psr-0" : {
"Raven_" : "lib/"
}
},
"extra": {
"branch-alias": {
"dev-master": "0.12.x-dev"
}
}
}

View File

@ -0,0 +1,43 @@
<?php
/*
* This file is part of Raven.
*
* (c) Sentry Team
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Autoloads Raven classes.
*
* @package raven
*/
class Raven_Autoloader
{
/**
* Registers Raven_Autoloader as an SPL autoloader.
*/
public static function register()
{
ini_set('unserialize_callback_func', 'spl_autoload_call');
spl_autoload_register(array('Raven_Autoloader', 'autoload'));
}
/**
* Handles autoloading of classes.
*
* @param string $class A class name.
*/
public static function autoload($class)
{
if (0 !== strpos($class, 'Raven')) {
return;
}
if (is_file($file = dirname(__FILE__).'/../'.str_replace(array('_', "\0"), array('/', ''), $class).'.php')) {
require $file;
}
}
}

View File

@ -0,0 +1,909 @@
<?php
/*
* This file is part of Raven.
*
* (c) Sentry Team
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Raven PHP Client
*
* @package raven
*/
class Raven_Client
{
const VERSION = '0.12.0';
const PROTOCOL = '6';
const DEBUG = 'debug';
const INFO = 'info';
const WARN = 'warning';
const WARNING = 'warning';
const ERROR = 'error';
const FATAL = 'fatal';
const MESSAGE_LIMIT = 1024;
public $severity_map;
public $extra_data;
public $store_errors_for_bulk_send = false;
public function __construct($options_or_dsn=null, $options=array())
{
if (is_null($options_or_dsn) && !empty($_SERVER['SENTRY_DSN'])) {
// Read from environment
$options_or_dsn = $_SERVER['SENTRY_DSN'];
}
if (!is_array($options_or_dsn)) {
if (!empty($options_or_dsn)) {
// Must be a valid DSN
$options_or_dsn = self::parseDSN($options_or_dsn);
} else {
$options_or_dsn = array();
}
}
$options = array_merge($options_or_dsn, $options);
$this->logger = Raven_Util::get($options, 'logger', 'php');
$this->servers = Raven_Util::get($options, 'servers');
$this->secret_key = Raven_Util::get($options, 'secret_key');
$this->public_key = Raven_Util::get($options, 'public_key');
$this->project = Raven_Util::get($options, 'project', 1);
$this->auto_log_stacks = (bool) Raven_Util::get($options, 'auto_log_stacks', false);
$this->name = Raven_Util::get($options, 'name', Raven_Compat::gethostname());
$this->site = Raven_Util::get($options, 'site', $this->_server_variable('SERVER_NAME'));
$this->tags = Raven_Util::get($options, 'tags', array());
$this->release = Raven_util::get($options, 'release', null);
$this->trace = (bool) Raven_Util::get($options, 'trace', true);
$this->timeout = Raven_Util::get($options, 'timeout', 2);
$this->message_limit = Raven_Util::get($options, 'message_limit', self::MESSAGE_LIMIT);
$this->exclude = Raven_Util::get($options, 'exclude', array());
$this->severity_map = null;
$this->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->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->processors = $this->setProcessorsFromOptions($options);
$this->_lasterror = null;
$this->_user = null;
$this->context = new Raven_Context();
if ($this->curl_method == 'async') {
$this->_curl_handler = new Raven_CurlHandler($this->get_curl_options());
}
}
public static function getDefaultProcessors()
{
return array(
'Raven_SanitizeDataProcessor',
);
}
/**
* Sets the Raven_Processor sub-classes to be used when data is processed before being
* sent to Sentry.
*
* @param $options
* @return array
*/
public function setProcessorsFromOptions($options)
{
$processors = array();
foreach (Raven_util::get($options, 'processors', self::getDefaultProcessors()) as $processor) {
$new_processor = new $processor($this);
if (isset($options['processorOptions']) && is_array($options['processorOptions'])) {
if (isset($options['processorOptions'][$processor]) && method_exists($processor, 'setProcessorOptions')) {
$new_processor->setProcessorOptions($options['processorOptions'][$processor]);
}
}
$processors[] = $new_processor;
}
return $processors;
}
/**
* Parses a Raven-compatible DSN and returns an array of its values.
*
* @param string $dsn Raven compatible DSN: http://raven.readthedocs.org/en/latest/config/#the-sentry-dsn
* @return array parsed DSN
*/
public static function parseDSN($dsn)
{
$url = parse_url($dsn);
$scheme = (isset($url['scheme']) ? $url['scheme'] : '');
if (!in_array($scheme, array('http', 'https'))) {
throw new InvalidArgumentException('Unsupported Sentry DSN scheme: ' . (!empty($scheme) ? $scheme : '<not set>'));
}
$netloc = (isset($url['host']) ? $url['host'] : null);
$netloc.= (isset($url['port']) ? ':'.$url['port'] : null);
$rawpath = (isset($url['path']) ? $url['path'] : null);
if ($rawpath) {
$pos = strrpos($rawpath, '/', 1);
if ($pos !== false) {
$path = substr($rawpath, 0, $pos);
$project = substr($rawpath, $pos + 1);
} else {
$path = '';
$project = substr($rawpath, 1);
}
} else {
$project = null;
$path = '';
}
$username = (isset($url['user']) ? $url['user'] : null);
$password = (isset($url['pass']) ? $url['pass'] : null);
if (empty($netloc) || empty($project) || empty($username) || empty($password)) {
throw new InvalidArgumentException('Invalid Sentry DSN: ' . $dsn);
}
return array(
'servers' => array(sprintf('%s://%s%s/api/%s/store/', $scheme, $netloc, $path, $project)),
'project' => $project,
'public_key' => $username,
'secret_key' => $password,
);
}
public function getLastError()
{
return $this->_lasterror;
}
/**
* Given an identifier, returns a Sentry searchable string.
*/
public function getIdent($ident)
{
// XXX: We don't calculate checksums yet, so we only have the ident.
return $ident;
}
/**
* Deprecated
*/
public function message($message, $params=array(), $level=self::INFO,
$stack=false, $vars = null)
{
return $this->captureMessage($message, $params, $level, $stack, $vars);
}
/**
* Deprecated
*/
public function exception($exception)
{
return $this->captureException($exception);
}
/**
* Log a message to sentry
*/
public function captureMessage($message, $params=array(), $level_or_options=array(),
$stack=false, $vars = null)
{
// Gracefully handle messages which contain formatting characters, but were not
// intended to be used with formatting.
if (!empty($params)) {
$formatted_message = vsprintf($message, $params);
} else {
$formatted_message = $message;
}
if ($level_or_options === null) {
$data = array();
} elseif (!is_array($level_or_options)) {
$data = array(
'level' => $level_or_options,
);
} else {
$data = $level_or_options;
}
$data['message'] = $formatted_message;
$data['sentry.interfaces.Message'] = array(
'message' => $message,
'params' => $params,
);
return $this->capture($data, $stack, $vars);
}
/**
* Log an exception to sentry
*/
public function captureException($exception, $culprit_or_options=null, $logger=null, $vars=null)
{
$has_chained_exceptions = version_compare(PHP_VERSION, '5.3.0', '>=');
if (in_array(get_class($exception), $this->exclude)) {
return null;
}
if (!is_array($culprit_or_options)) {
$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(),
'type' => get_class($exc),
'module' => $exc->getFile() .':'. $exc->getLine(),
);
/**'sentry.interfaces.Exception'
* Exception::getTrace doesn't store the point at where the exception
* was thrown, so we have to stuff it in ourselves. Ugh.
*/
$trace = $exc->getTrace();
$frame_where_exception_thrown = array(
'file' => $exc->getFile(),
'line' => $exc->getLine(),
);
array_unshift($trace, $frame_where_exception_thrown);
// manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149)
if (!class_exists('Raven_Stacktrace')) {
spl_autoload_call('Raven_Stacktrace');
}
$exc_data['stacktrace'] = array(
'frames' => Raven_Stacktrace::get_stack_info(
$trace, $this->trace, $this->shift_vars, $vars, $this->message_limit
),
);
$exceptions[] = $exc_data;
} while ($has_chained_exceptions && $exc = $exc->getPrevious());
$data['message'] = $message;
$data['sentry.interfaces.Exception'] = array(
'values' => array_reverse($exceptions),
);
if ($logger !== null) {
$data['logger'] = $logger;
}
if (empty($data['level'])) {
if (method_exists($exception, 'getSeverity')) {
$data['level'] = $this->translateSeverity($exception->getSeverity());
} else {
$data['level'] = self::ERROR;
}
}
return $this->capture($data, $trace, $vars);
}
/**
* Log an query to sentry
*/
public function captureQuery($query, $level=self::INFO, $engine = '')
{
$data = array(
'message' => $query,
'level' => $level,
'sentry.interfaces.Query' => array(
'query' => $query
)
);
if ($engine !== '') {
$data['sentry.interfaces.Query']['engine'] = $engine;
}
return $this->capture($data, false);
}
protected function is_http_request()
{
return isset($_SERVER['REQUEST_METHOD']) && PHP_SAPI !== 'cli';
}
protected function get_http_data()
{
$env = $headers = array();
foreach ($_SERVER as $key => $value) {
if (0 === strpos($key, 'HTTP_')) {
if (in_array($key, array('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'))) {
continue;
}
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))))] = $value;
} elseif (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH'))) {
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key))))] = $value;
} else {
$env[$key] = $value;
}
}
$result = array(
'method' => $this->_server_variable('REQUEST_METHOD'),
'url' => $this->get_current_url(),
'query_string' => $this->_server_variable('QUERY_STRING'),
);
// dont set this as an empty array as PHP will treat it as a numeric array
// instead of a mapping which goes against the defined Sentry spec
if (!empty($_POST)) {
$result['data'] = $_POST;
}
if (!empty($_COOKIE)) {
$result['cookies'] = $_COOKIE;
}
if (!empty($headers)) {
$result['headers'] = $headers;
}
if (!empty($env)) {
$result['env'] = $env;
}
return array(
'sentry.interfaces.Http' => $result,
);
}
protected function get_user_data()
{
$user = $this->context->user;
if ($user === null) {
if (!session_id()) {
return array();
}
$user = array(
'id' => session_id(),
);
if (!empty($_SESSION)) {
$user['data'] = $_SESSION;
}
}
return array(
'sentry.interfaces.User' => $user,
);
}
protected function get_extra_data()
{
return $this->extra_data;
}
public function get_default_data()
{
return array(
'server_name' => $this->name,
'project' => $this->project,
'site' => $this->site,
'logger' => $this->logger,
'tags' => $this->tags,
'platform' => 'php',
);
}
public function capture($data, $stack, $vars = null)
{
if (!isset($data['timestamp'])) {
$data['timestamp'] = gmdate('Y-m-d\TH:i:s\Z');
}
if (!isset($data['level'])) {
$data['level'] = self::ERROR;
}
if (!isset($data['tags'])) {
$data['tags'] = array();
}
if (!isset($data['extra'])) {
$data['extra'] = array();
}
if (!isset($data['event_id'])) {
$data['event_id'] = $this->uuid4();
}
if (isset($data['message'])) {
$data['message'] = substr($data['message'], 0, $this->message_limit);
}
$data = array_merge($this->get_default_data(), $data);
if ($this->is_http_request()) {
$data = array_merge($this->get_http_data(), $data);
}
$data = array_merge($this->get_user_data(), $data);
if ($this->release) {
$data['release'] = $this->release;
}
$data['tags'] = array_merge(
$this->tags,
$this->context->tags,
$data['tags']);
$data['extra'] = array_merge(
$this->get_extra_data(),
$this->context->extra,
$data['extra']);
if ((!$stack && $this->auto_log_stacks) || $stack === true) {
$stack = debug_backtrace();
// Drop last stack
array_shift($stack);
}
if (!empty($stack)) {
// manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149)
if (!class_exists('Raven_Stacktrace')) {
spl_autoload_call('Raven_Stacktrace');
}
if (!isset($data['sentry.interfaces.Stacktrace'])) {
$data['sentry.interfaces.Stacktrace'] = array(
'frames' => Raven_Stacktrace::get_stack_info(
$stack, $this->trace, $this->shift_vars, $vars, $this->message_limit
),
);
}
}
$this->sanitize($data);
$this->process($data);
if (!$this->store_errors_for_bulk_send) {
$this->send($data);
} else {
if (empty($this->error_data)) {
$this->error_data = array();
}
$this->error_data[] = $data;
}
return $data['event_id'];
}
public function sanitize(&$data)
{
// manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149)
if (!class_exists('Raven_Serializer')) {
spl_autoload_call('Raven_Serializer');
}
$data = Raven_Serializer::serialize($data);
}
/**
* Process data through all defined Raven_Processor sub-classes
*
* @param array $data Associative array of data to log
*/
public function process(&$data)
{
foreach ($this->processors as $processor) {
$processor->process($data);
}
}
public function sendUnsentErrors()
{
if (!empty($this->error_data)) {
foreach ($this->error_data as $data) {
$this->send($data);
}
unset($this->error_data);
}
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');
}
}
/**
* Wrapper to handle encoding and sending data to all defined Sentry servers
*
* @param array $data Associative array of data to log
*/
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;
}
if (!$this->servers) {
return;
}
$message = Raven_Compat::json_encode($data);
if (function_exists("gzcompress")) {
$message = gzcompress($message);
}
$message = base64_encode($message); // PHP's builtin curl_* function are happy without this, but the exec method requires it
foreach ($this->servers as $url) {
$client_string = 'raven-php/' . self::VERSION;
$timestamp = microtime(true);
$headers = array(
'User-Agent' => $client_string,
'X-Sentry-Auth' => $this->get_auth_header(
$timestamp, $client_string, $this->public_key,
$this->secret_key),
'Content-Type' => 'application/octet-stream'
);
$this->send_remote($url, $message, $headers);
}
}
/**
* Send data to Sentry
*
* @param string $url Full URL to Sentry
* @param array $data Associative array of data to log
* @param array $headers Associative array of headers
*/
private function send_remote($url, $data, $headers=array())
{
$parts = parse_url($url);
$parts['netloc'] = $parts['host'].(isset($parts['port']) ? ':'.$parts['port'] : null);
$this->send_http($url, $data, $headers);
}
protected function get_default_ca_cert()
{
return dirname(__FILE__) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cacert.pem';
}
protected function get_curl_options()
{
$options = array(
CURLOPT_VERBOSE => false,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_SSL_VERIFYPEER => $this->verify_ssl,
CURLOPT_CAINFO => $this->ca_cert,
CURLOPT_USERAGENT => 'raven-php/' . self::VERSION,
);
if ($this->http_proxy) {
$options[CURLOPT_PROXY] = $this->http_proxy;
}
if ($this->curl_ssl_version) {
$options[CURLOPT_SSLVERSION] = $this->curl_ssl_version;
}
if ($this->curl_ipv4) {
$options[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
}
if (defined('CURLOPT_TIMEOUT_MS')) {
// MS is available in curl >= 7.16.2
$timeout = max(1, ceil(1000 * $this->timeout));
// some versions of PHP 5.3 don't have this defined correctly
if (!defined('CURLOPT_CONNECTTIMEOUT_MS')) {
//see http://stackoverflow.com/questions/9062798/php-curl-timeout-is-not-working/9063006#9063006
define('CURLOPT_CONNECTTIMEOUT_MS', 156);
}
$options[CURLOPT_CONNECTTIMEOUT_MS] = $timeout;
$options[CURLOPT_TIMEOUT_MS] = $timeout;
} else {
// fall back to the lower-precision timeout.
$timeout = max(1, ceil($this->timeout));
$options[CURLOPT_CONNECTTIMEOUT] = $timeout;
$options[CURLOPT_TIMEOUT] = $timeout;
}
return $options;
}
/**
* Send the message over http to the sentry url given
*
* @param string $url URL of the Sentry instance to log to
* @param array $data Associative array of data to log
* @param array $headers Associative array of headers
*/
private function send_http($url, $data, $headers=array())
{
if ($this->curl_method == 'async') {
$this->_curl_handler->enqueue($url, $data, $headers);
} elseif ($this->curl_method == 'exec') {
$this->send_http_asynchronous_curl_exec($url, $data, $headers);
} else {
$this->send_http_synchronous($url, $data, $headers);
}
}
/**
* Send the cURL to Sentry asynchronously. No errors will be returned from cURL
*
* @param string $url URL of the Sentry instance to log to
* @param array $data Associative array of data to log
* @param array $headers Associative array of headers
* @return bool
*/
private function send_http_asynchronous_curl_exec($url, $data, $headers)
{
// TODO(dcramer): support ca_cert
$cmd = $this->curl_path.' -X POST ';
foreach ($headers as $key => $value) {
$cmd .= '-H \''. $key. ': '. $value. '\' ';
}
$cmd .= '-d \''. $data .'\' ';
$cmd .= '\''. $url .'\' ';
$cmd .= '-m 5 '; // 5 second timeout for the whole process (connect + send)
$cmd .= '> /dev/null 2>&1 &'; // ensure exec returns immediately while curl runs in the background
exec($cmd);
return true; // The exec method is just fire and forget, so just assume it always works
}
/**
* Send a blocking cURL to Sentry and check for errors from cURL
*
* @param string $url URL of the Sentry instance to log to
* @param array $data Associative array of data to log
* @param array $headers Associative array of headers
* @return bool
*/
private function send_http_synchronous($url, $data, $headers)
{
$new_headers = array();
foreach ($headers as $key => $value) {
array_push($new_headers, $key .': '. $value);
}
// XXX(dcramer): Prevent 100-continue response form server (Fixes GH-216)
$new_headers[] = 'Expect:';
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_HTTPHEADER, $new_headers);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$options = $this->get_curl_options();
$ca_cert = $options[CURLOPT_CAINFO];
unset($options[CURLOPT_CAINFO]);
curl_setopt_array($curl, $options);
curl_exec($curl);
$errno = curl_errno($curl);
// CURLE_SSL_CACERT || CURLE_SSL_CACERT_BADFILE
if ($errno == 60 || $errno == 77) {
curl_setopt($curl, CURLOPT_CAINFO, $ca_cert);
curl_exec($curl);
}
$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$success = ($code == 200);
if (!$success) {
// It'd be nice just to raise an exception here, but it's not very PHP-like
$this->_lasterror = curl_error($curl);
} else {
$this->_lasterror = null;
}
curl_close($curl);
return $success;
}
/**
* Generate a Sentry authorization header string
*
* @param string $timestamp Timestamp when the event occurred
* @param string $client HTTP client name (not Raven_Client object)
* @param string $api_key Sentry API key
* @param string $secret_key Sentry API key
* @return string
*/
protected function get_auth_header($timestamp, $client, $api_key, $secret_key)
{
$header = array(
sprintf('sentry_timestamp=%F', $timestamp),
"sentry_client={$client}",
sprintf('sentry_version=%s', self::PROTOCOL),
);
if ($api_key) {
$header[] = "sentry_key={$api_key}";
}
if ($secret_key) {
$header[] = "sentry_secret={$secret_key}";
}
return sprintf('Sentry %s', implode(', ', $header));
}
/**
* Generate an uuid4 value
*
* @return string
*/
private function uuid4()
{
$uuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
// 32 bits for "time_low"
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
// 16 bits for "time_mid"
mt_rand(0, 0xffff),
// 16 bits for "time_hi_and_version",
// four most significant bits holds version number 4
mt_rand(0, 0x0fff) | 0x4000,
// 16 bits, 8 bits for "clk_seq_hi_res",
// 8 bits for "clk_seq_low",
// two most significant bits holds zero and one for variant DCE1.1
mt_rand(0, 0x3fff) | 0x8000,
// 48 bits for "node"
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
return str_replace('-', '', $uuid);
}
/**
* Return the URL for the current request
*
* @return string|null
*/
private function get_current_url()
{
// When running from commandline the REQUEST_URI is missing.
if (!isset($_SERVER['REQUEST_URI'])) {
return null;
}
$schema = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'
|| $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
// HTTP_HOST is a client-supplied header that is optional in HTTP 1.0
$host = (!empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST']
: (!empty($_SERVER['LOCAL_ADDR']) ? $_SERVER['LOCAL_ADDR']
: (!empty($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '')));
return $schema . $host . $_SERVER['REQUEST_URI'];
}
/**
* Get the value of a key from $_SERVER
*
* @param string $key Key whose value you wish to obtain
* @return string Key's value
*/
private function _server_variable($key)
{
if (isset($_SERVER[$key])) {
return $_SERVER[$key];
}
return '';
}
/**
* Translate a PHP Error constant into a Sentry log level group
*
* @param string $severity PHP E_$x error constant
* @return string Sentry log level group
*/
public function translateSeverity($severity)
{
if (is_array($this->severity_map) && isset($this->severity_map[$severity])) {
return $this->severity_map[$severity];
}
switch ($severity) {
case E_ERROR: return Raven_Client::ERROR;
case E_WARNING: return Raven_Client::WARN;
case E_PARSE: return Raven_Client::ERROR;
case E_NOTICE: return Raven_Client::INFO;
case E_CORE_ERROR: return Raven_Client::ERROR;
case E_CORE_WARNING: return Raven_Client::WARN;
case E_COMPILE_ERROR: return Raven_Client::ERROR;
case E_COMPILE_WARNING: return Raven_Client::WARN;
case E_USER_ERROR: return Raven_Client::ERROR;
case E_USER_WARNING: return Raven_Client::WARN;
case E_USER_NOTICE: return Raven_Client::INFO;
case E_STRICT: return Raven_Client::INFO;
case E_RECOVERABLE_ERROR: return Raven_Client::ERROR;
}
if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
switch ($severity) {
case E_DEPRECATED: return Raven_Client::WARN;
case E_USER_DEPRECATED: return Raven_Client::WARN;
}
}
return Raven_Client::ERROR;
}
/**
* Provide a map of PHP Error constants to Sentry logging groups to use instead
* of the defaults in translateSeverity()
*
* @param array $map
*/
public function registerSeverityMap($map)
{
$this->severity_map = $map;
}
/**
* Convenience function for setting a user's ID and Email
*
* @param string $id User's ID
* @param string|null $email User's email
* @param array $data Additional user data
*/
public function set_user_data($id, $email=null, $data=array())
{
$this->user_context(array_merge(array(
'id' => $id,
'email' => $email,
), $data));
}
/**
* Sets user context.
*
* @param array $data Associative array of user data
*/
public function user_context($data)
{
$this->context->user = $data;
}
/**
* Appends tags context.
*
* @param array $data Associative array of tags
*/
public function tags_context($data)
{
$this->context->tags = array_merge($this->context->tags, $data);
}
/**
* Appends additional context.
*
* @param array $data Associative array of extra data
*/
public function extra_context($data)
{
$this->context->extra = array_merge($this->context->extra, $data);
}
/**
* @param array $processors
*/
public function setProcessors(array $processors)
{
$this->processors = $processors;
}
}

View File

@ -0,0 +1,136 @@
<?php
/*
* This file is part of Raven.
*
* (c) Sentry Team
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Raven_Compat
{
public static function gethostname()
{
if (function_exists('gethostname')) {
return gethostname();
}
return self::_gethostname();
}
public static function _gethostname()
{
return php_uname('n');
}
public static function hash_hmac($algo, $data, $key, $raw_output=false)
{
if (function_exists('hash_hmac')) {
return hash_hmac($algo, $data, $key, $raw_output);
}
return self::_hash_hmac($algo, $data, $key, $raw_output);
}
/**
* Implementation from 'KC Cloyd'.
* See http://nl2.php.net/manual/en/function.hash-hmac.php
*/
public static function _hash_hmac($algo, $data, $key, $raw_output=false)
{
$algo = strtolower($algo);
$pack = 'H'.strlen($algo('test'));
$size = 64;
$opad = str_repeat(chr(0x5C), $size);
$ipad = str_repeat(chr(0x36), $size);
if (strlen($key) > $size) {
$key = str_pad(pack($pack, $algo($key)), $size, chr(0x00));
} else {
$key = str_pad($key, $size, chr(0x00));
}
$keyLastPos = strlen($key) - 1;
for ($i = 0; $i < $keyLastPos; $i++) {
$opad[$i] = $opad[$i] ^ $key[$i];
$ipad[$i] = $ipad[$i] ^ $key[$i];
}
$output = $algo($opad.pack($pack, $algo($ipad.$data)));
return ($raw_output) ? pack($pack, $output) : $output;
}
/**
* Note that we discard the options given to be compatible
* with PHP < 5.3
*/
public static function json_encode($value, $options=0)
{
if (function_exists('json_encode')) {
return json_encode($value);
}
return self::_json_encode($value);
}
/**
* Implementation taken from
* http://www.mike-griffiths.co.uk/php-json_encode-alternative/
*/
public static function _json_encode($value)
{
static $jsonReplaces = array(
array('\\', '/', "\n", "\t", "\r", "\b", "\f", '"'),
array('\\\\', '\\/', '\\n', '\\t', '\\r', '\\b', '\\f', '\"'));
if (is_null($value)) {
return 'null';
}
if ($value === false) {
return 'false';
}
if ($value === true) {
return 'true';
}
if (is_scalar($value)) {
// Always use '.' for floats.
if (is_float($value)) {
return floatval(str_replace(',', '.', strval($value)));
}
if (is_string($value)) {
return sprintf('"%s"',
str_replace($jsonReplaces[0], $jsonReplaces[1], $value));
} else {
return $value;
}
}
$isList = true;
for ($i = 0, reset($value); $i<count($value); $i++, next($value)) {
if (key($value) !== $i) {
$isList = false;
break;
}
}
$result = array();
if ($isList) {
foreach ($value as $v) {
$result[] = self::_json_encode($v);
}
return '[' . join(',', $result) . ']';
} else {
foreach ($value as $k => $v) {
$result[] = self::_json_encode($k) . ':' . self::_json_encode($v);
}
return '{' . join(',', $result) . '}';
}
}
}

View File

@ -0,0 +1,23 @@
<?php
/**
* Storage for additional client context.
*
* @package raven
*/
class Raven_Context
{
public function __construct()
{
$this->clear();
}
/**
* Clean up existing context.
*/
public function clear()
{
$this->tags = array();
$this->extra = array();
$this->user = null;
}
}

View File

@ -0,0 +1,117 @@
<?php
/*
* This file is part of Raven.
*
* (c) Sentry Team
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Asynchronous Curl connection manager.
*
* @package raven
*/
// TODO(dcramer): handle ca_cert
class Raven_CurlHandler
{
private $join_timeout;
private $multi_handle;
private $options;
private $requests;
public function __construct($options, $join_timeout=5)
{
$this->options = $options;
$this->multi_handle = curl_multi_init();
$this->requests = array();
$this->join_timeout = 5;
register_shutdown_function(array($this, 'join'));
}
public function __destruct()
{
$this->join();
}
public function enqueue($url, $data=null, $headers=array())
{
$ch = curl_init();
$new_headers = array();
foreach ($headers as $key => $value) {
array_push($new_headers, $key .': '. $value);
}
// XXX(dcramer): Prevent 100-continue response form server (Fixes GH-216)
$new_headers[] = 'Expect:';
curl_setopt($ch, CURLOPT_HTTPHEADER, $new_headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt_array($ch, $this->options);
if (isset($data)) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
}
curl_multi_add_handle($this->multi_handle, $ch);
$fd = (int)$ch;
$this->requests[$fd] = 1;
$this->select();
return $fd;
}
public function join($timeout=null)
{
if (!isset($timeout)) {
$timeout = $this->join_timeout;
}
$start = time();
do {
$this->select();
if (count($this->requests) === 0) {
break;
}
usleep(10000);
} while ($timeout !== 0 && time() - $start > $timeout);
}
// http://se2.php.net/manual/en/function.curl-multi-exec.php
private function select()
{
do {
$mrc = curl_multi_exec($this->multi_handle, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
while ($active && $mrc == CURLM_OK) {
if (curl_multi_select($this->multi_handle) !== -1) {
do {
$mrc = curl_multi_exec($this->multi_handle, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
} else {
return;
}
}
while ($info = curl_multi_info_read($this->multi_handle)) {
$ch = $info['handle'];
$fd = (int)$ch;
curl_multi_remove_handle($this->multi_handle, $ch);
if (!isset($this->requests[$fd])) {
return;
}
unset($this->requests[$fd]);
}
}
}

View File

@ -0,0 +1,175 @@
<?php
/*
* This file is part of Raven.
*
* (c) Sentry Team
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Event handlers for exceptions and errors
*
* $client = new Raven_Client('http://public:secret/example.com/1');
* $error_handler = new Raven_ErrorHandler($client);
* $error_handler->registerExceptionHandler();
* $error_handler->registerErrorHandler();
* $error_handler->registerShutdownFunction();
*
* @package raven
*/
class Raven_ErrorHandler
{
private $old_exception_handler;
private $call_existing_exception_handler = false;
private $old_error_handler;
private $call_existing_error_handler = false;
private $reservedMemory;
private $send_errors_last = false;
private $error_types = -1;
/**
* @var array
* Error types that can be processed by the handler
*/
private $validErrorTypes = array(
E_ERROR,
E_WARNING,
E_PARSE,
E_NOTICE,
E_CORE_ERROR,
E_CORE_WARNING,
E_COMPILE_ERROR,
E_COMPILE_WARNING,
E_USER_ERROR,
E_USER_WARNING,
E_USER_NOTICE,
E_STRICT,
E_RECOVERABLE_ERROR,
E_DEPRECATED,
E_USER_DEPRECATED,
);
/**
* @var array
* Error types that are always processed by the handler
*/
private $defaultErrorTypes = array(
E_ERROR,
E_PARSE,
E_CORE_ERROR,
E_CORE_WARNING,
E_COMPILE_ERROR,
E_COMPILE_WARNING,
E_STRICT,
);
public function __construct($client, $send_errors_last = false)
{
$this->client = $client;
register_shutdown_function(array($this, 'detectShutdown'));
if ($send_errors_last) {
$this->send_errors_last = true;
$this->client->store_errors_for_bulk_send = true;
register_shutdown_function(array($this->client, 'sendUnsentErrors'));
}
}
public function handleException($e, $isError = false, $vars = null)
{
$e->event_id = $this->client->getIdent($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())
{
if ($this->error_types & $code & error_reporting()) {
$e = new ErrorException($message, 0, $code, $file, $line);
$this->handleException($e, true, $context);
}
if ($this->call_existing_error_handler) {
if ($this->old_error_handler) {
return call_user_func($this->old_error_handler, $code, $message, $file, $line, $context);
} else {
return false;
}
}
}
/**
* Nothing by default, use it in child classes for catching other types of errors
* Only constants from $this->validErrorTypes can be used
*
* @return array
*/
protected function getAdditionalErrorTypesToProcess()
{
return array();
}
/**
* @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($this->defaultErrorTypes + $additionalErrorTypes);
}
public function handleFatalError()
{
if (null === $lastError = error_get_last()) {
return;
}
unset($this->reservedMemory);
$errors = 0;
foreach ($this->getErrorTypesToProcess() as $errorType) {
$errors |= $errorType;
}
if ($lastError['type'] & $errors) {
$e = new ErrorException(
@$lastError['message'], @$lastError['type'], @$lastError['type'],
@$lastError['file'], @$lastError['line']
);
$this->handleException($e, true);
}
}
public function registerExceptionHandler($call_existing_exception_handler = true)
{
$this->old_exception_handler = set_exception_handler(array($this, 'handleException'));
$this->call_existing_exception_handler = $call_existing_exception_handler;
}
public function registerErrorHandler($call_existing_error_handler = true, $error_types = -1)
{
$this->error_types = $error_types;
$this->old_error_handler = set_error_handler(array($this, 'handleError'), error_reporting());
$this->call_existing_error_handler = $call_existing_error_handler;
}
public function registerShutdownFunction($reservedMemorySize = 10)
{
register_shutdown_function(array($this, 'handleFatalError'));
$this->reservedMemory = str_repeat('x', 1024 * $reservedMemorySize);
}
public function detectShutdown()
{
if (!defined('RAVEN_CLIENT_END_REACHED')) {
define('RAVEN_CLIENT_END_REACHED', true);
}
}
}

View File

@ -0,0 +1,20 @@
<?php
/**
* Base class for data processing.
*
* @package raven
*/
abstract class Raven_Processor
{
public function __construct(Raven_Client $client)
{
$this->client = $client;
}
/**
* Process and sanitize data, modifying the existing value if necessary.
*
* @param array $data Array of log data
*/
abstract public function process(&$data);
}

View File

@ -0,0 +1,102 @@
<?php
/**
* Asterisk out passwords from password fields in frames, http,
* and basic extra data.
*
* @package raven
*/
class Raven_SanitizeDataProcessor extends Raven_Processor
{
const MASK = '********';
const FIELDS_RE = '/(authorization|password|passwd|secret|password_confirmation|card_number|auth_pw)/i';
const VALUES_RE = '/^(?:\d[ -]*?){13,16}$/';
private $client;
private $fields_re;
private $values_re;
public function __construct(Raven_Client $client)
{
$this->client = $client;
$this->fields_re = self::FIELDS_RE;
$this->values_re = self::VALUES_RE;
}
/**
* Override the default processor options
*
* @param array $options Associative array of processor options
*/
public function setProcessorOptions(array $options)
{
if (isset($options['fields_re'])) {
$this->fields_re = $options['fields_re'];
}
if (isset($options['values_re'])) {
$this->values_re = $options['values_re'];
}
}
/**
* Replace any array values with our mask if the field name or the value matches a respective regex
*
* @param mixed $item Associative array value
* @param string $key Associative array key
*/
public function sanitize(&$item, $key)
{
if (empty($item)) {
return;
}
if (preg_match($this->values_re, $item)) {
$item = self::MASK;
}
if (empty($key)) {
return;
}
if (preg_match($this->fields_re, $key)) {
$item = self::MASK;
}
}
public function process(&$data)
{
array_walk_recursive($data, array($this, 'sanitize'));
}
/**
* @return string
*/
public function getFieldsRe()
{
return $this->fields_re;
}
/**
* @param string $fields_re
*/
public function setFieldsRe($fields_re)
{
$this->fields_re = $fields_re;
}
/**
* @return string
*/
public function getValuesRe()
{
return $this->values_re;
}
/**
* @param string $values_re
*/
public function setValuesRe($values_re)
{
$this->values_re = $values_re;
}
}

View File

@ -0,0 +1,77 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* This helper is based on code from Facebook's Phabricator project
*
* https://github.com/facebook/phabricator
*
* Specifically, it is an adaptation of the PhutilReadableSerializer class.
*
* @package raven
*/
class Raven_Serializer
{
/**
* Serialize an object (recursively) into something safe for data
* sanitization and encoding.
*/
public static function serialize($value, $max_depth=9, $_depth=0)
{
if (is_object($value) || is_resource($value)) {
return self::serializeValue($value);
} elseif ($_depth < $max_depth && is_array($value)) {
$new = array();
foreach ($value as $k => $v) {
$new[self::serializeValue($k)] = self::serialize($v, $max_depth, $_depth + 1);
}
return $new;
} else {
return self::serializeValue($value);
}
}
public static function serializeValue($value)
{
if ($value === null) {
return 'null';
} elseif ($value === false) {
return 'false';
} elseif ($value === true) {
return 'true';
} elseif (is_float($value) && (int) $value == $value) {
return $value.'.0';
} elseif (is_object($value) || gettype($value) == 'object') {
return 'Object '.get_class($value);
} elseif (is_resource($value)) {
return 'Resource '.get_resource_type($value);
} elseif (is_array($value)) {
return 'Array of length ' . count($value);
} elseif (is_integer($value)) {
return (integer) $value;
} else {
$value = (string) $value;
if (function_exists('mb_convert_encoding')) {
$value = mb_convert_encoding($value, 'UTF-8', 'UTF-8');
}
return $value;
}
}
}

View File

@ -0,0 +1,252 @@
<?php
/**
* Small helper class to inspect the stacktrace
*
* @package raven
*/
class Raven_Stacktrace
{
public static $statements = array(
'include',
'include_once',
'require',
'require_once',
);
public static function get_stack_info($frames, $trace = false, $shiftvars = true, $errcontext = null,
$frame_var_limit = Raven_Client::MESSAGE_LIMIT)
{
/**
* 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.
*/
$result = array();
for ($i = 0; $i < count($frames); $i++) {
$frame = $frames[$i];
$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;
} else {
$context = self::read_source_file($frame['file'], $frame['line']);
$abs_path = $frame['file'];
$filename = basename($frame['file']);
}
$module = $filename;
if (isset($nextframe['class'])) {
$module .= ':' . $nextframe['class'];
}
if (empty($result) && 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);
}
} else {
$vars = array();
}
}
$frame = array(
'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'],
);
// 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)) {
foreach ($vars as $key => $value) {
if (is_string($value) || is_numeric($value)) {
$vars[$key] = substr($value, 0, $frame_var_limit);
}
}
$frame['vars'] = $vars;
}
$result[] = $frame;
}
return array_reverse($result);
}
public static function get_caller_frame_context($frame)
{
if (!isset($frame['args'])) {
return array();
}
$i = 1;
$args = array();
foreach ($frame['args'] as $arg) {
$args['param'.$i] = $arg;
$i++;
}
return $args;
}
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();
}
if (strpos($frame['function'], '__lambda_func') !== false) {
return array();
}
if (isset($frame['class']) && $frame['class'] == 'Closure') {
return array();
}
if (strpos($frame['function'], '{closure}') !== false) {
return array();
}
if (in_array($frame['function'], self::$statements)) {
if (empty($frame['args'])) {
// No arguments
return array();
} else {
// Sanitize the file path
return array($frame['args'][0]);
}
}
try {
if (isset($frame['class'])) {
if (method_exists($frame['class'], $frame['function'])) {
$reflection = new ReflectionMethod($frame['class'], $frame['function']);
} elseif ($frame['type'] === '::') {
$reflection = new ReflectionMethod($frame['class'], '__callStatic');
} else {
$reflection = new ReflectionMethod($frame['class'], '__call');
}
} else {
$reflection = new ReflectionFunction($frame['function']);
}
} catch (ReflectionException $e) {
return array();
}
$params = $reflection->getParameters();
$args = array();
foreach ($frame['args'] as $i => $arg) {
if (isset($params[$i])) {
// Assign the argument by the parameter name
if (is_array($arg)) {
foreach ($arg as $key => $value) {
if (is_string($value) || is_numeric($value)) {
$arg[$key] = substr($value, 0, $frame_arg_limit);
}
}
}
$args[$params[$i]->name] = $arg;
} else {
// TODO: Sentry thinks of these as context locals, so they must be named
// Assign the argument by number
// $args[$i] = $arg;
}
}
return $args;
}
private static function read_source_file($filename, $lineno, $context_lines = 5)
{
$frame = array(
'prefix' => array(),
'line' => '',
'suffix' => array(),
'filename' => $filename,
'lineno' => $lineno,
);
if ($filename === null || $lineno === null) {
return $frame;
}
// Code which is eval'ed have a modified filename.. Extract the
// correct filename + linenumber from the string.
$matches = array();
$matched = preg_match("/^(.*?)\((\d+)\) : eval\(\)'d code$/",
$filename, $matches);
if ($matched) {
$frame['filename'] = $filename = $matches[1];
$frame['lineno'] = $lineno = $matches[2];
}
// In the case of an anonymous function, the filename is sent as:
// "</path/to/filename>(<lineno>) : runtime-created function"
// Extract the correct filename + linenumber from the string.
$matches = array();
$matched = preg_match("/^(.*?)\((\d+)\) : runtime-created function$/",
$filename, $matches);
if ($matched) {
$frame['filename'] = $filename = $matches[1];
$frame['lineno'] = $lineno = $matches[2];
}
try {
$file = new SplFileObject($filename);
$target = max(0, ($lineno - ($context_lines + 1)));
$file->seek($target);
$cur_lineno = $target+1;
while (!$file->eof()) {
$line = rtrim($file->current(), "\r\n");
if ($cur_lineno == $lineno) {
$frame['line'] = $line;
} elseif ($cur_lineno < $lineno) {
$frame['prefix'][] = $line;
} elseif ($cur_lineno > $lineno) {
$frame['suffix'][] = $line;
}
$cur_lineno++;
if ($cur_lineno > $lineno + $context_lines) {
break;
}
$file->next();
}
} catch (RuntimeException $exc) {
return $frame;
}
return $frame;
}
}

View File

@ -0,0 +1,33 @@
<?php
/*
* This file is part of Raven.
*
* (c) Sentry Team
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Utilities
*
* @package raven
*/
class Raven_Util
{
/**
* Because we love Python, this works much like dict.get() in Python.
*
* Returns $var from $array if set, otherwise returns $default.
*/
public static function get($array, $var, $default=null)
{
if (isset($array[$var])) {
return $array[$var];
}
return $default;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="test/bootstrap.php"
>
<testsuites>
<testsuite name="Raven Test Suite">
<directory>./test/Raven/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">./lib/Raven/</directory>
</whitelist>
</filter>
</phpunit>

View File

@ -0,0 +1,624 @@
<?php
/*
* This file is part of Raven.
*
* (c) Sentry Team
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
// XXX: Is there a better way to stub the client?
class Dummy_Raven_Client extends Raven_Client
{
private $__sent_events = array();
public function getSentEvents()
{
return $this->__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();
}
}
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['servers'], array('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['servers'], array('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['servers'], array('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['servers'], array('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->servers, array('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->servers, array('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(
'servers' => array('http://example.com/api/1/store/'),
'project' => 1,
));
$this->assertEquals($client->servers, array('http://example.com/api/1/store/'));
}
public function testOptionsFirstArgumentWithOptions()
{
$client = new Raven_Client(array(
'servers' => array('http://example.com/api/1/store/'),
'project' => 1,
), array(
'site' => 'foo',
));
$this->assertEquals($client->servers, array('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 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['sentry.interfaces.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['sentry.interfaces.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,
);
$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',
'HTTP_CONTENT_TYPE' => 'text/html',
'HTTP_CONTENT_LENGTH' => '1000',
'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(
'sentry.interfaces.Http' => 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(
'sentry.interfaces.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(
'sentry.interfaces.User' => array(
'id' => session_id(),
)
);
$this->assertEquals($expected, $client->get_user_data());
}
public function testGetAuthHeader()
{
$client = new Dummy_Raven_Client();
$clientstring = 'raven-php/test';
$timestamp = '1234341324.340000';
$expected = "Sentry sentry_timestamp={$timestamp}, sentry_client={$clientstring}, " .
"sentry_version=" . Dummy_Raven_Client::PROTOCOL . ", " .
"sentry_key=publickey, sentry_secret=secretkey";
$this->assertEquals($expected, $client->get_auth_header($timestamp, 'raven-php/test', 'publickey', 'secretkey'));
}
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['sentry.interfaces.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 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));
}
}

View File

@ -0,0 +1,46 @@
<?php
/*
* This file is part of Raven.
*
* (c) Sentry Team
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Raven_Tests_CompatTest extends PHPUnit_Framework_TestCase
{
public function test_gethostname()
{
$this->assertEquals(Raven_Compat::gethostname(), Raven_Compat::_gethostname());
$this->assertTrue(strlen(Raven_Compat::_gethostname()) > 0);
}
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);
}
}

View File

@ -0,0 +1,87 @@
<?php
/*
* This file is part of Raven.
*
* (c) Sentry Team
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Raven_Tests_ErrorHandlerTest extends PHPUnit_Framework_TestCase
{
private $errorLevel;
public function setUp()
{
$this->errorLevel = error_reporting();
}
public function tearDown()
{
error_reporting($this->errorLevel);
}
public function testErrorsAreLoggedAsExceptions()
{
$client = $this->getMock('Client', array('captureException', 'getIdent'));
$client->expects($this->once())
->method('captureException')
->with($this->isInstanceOf('ErrorException'));
$handler = new Raven_ErrorHandler($client);
$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 testErrorHandlerCheckSilentReporting()
{
$client = $this->getMock('Client', array('captureException', 'getIdent'));
$client->expects($this->never())
->method('captureException');
$handler = new Raven_ErrorHandler($client);
$handler->registerErrorHandler(false);
@trigger_error('Silent', E_USER_WARNING);
}
public function testErrorHandlerBlockErrorReporting()
{
$client = $this->getMock('Client', array('captureException', 'getIdent'));
$client->expects($this->never())
->method('captureException');
$handler = new Raven_ErrorHandler($client);
$handler->registerErrorHandler(false);
error_reporting(E_USER_ERROR);
trigger_error('Warning', E_USER_WARNING);
}
public function testErrorHandlerPassErrorReportingPass()
{
$client = $this->getMock('Client', array('captureException', 'getIdent'));
$client->expects($this->once())
->method('captureException');
$handler = new Raven_ErrorHandler($client);
$handler->registerErrorHandler(false);
error_reporting(E_USER_WARNING);
trigger_error('Warning', E_USER_WARNING);
}
}

View File

@ -0,0 +1,180 @@
<?php
/*
* This file is part of Raven.
*
* (c) Sentry Team
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Raven_Tests_SanitizeDataProcessorTest extends PHPUnit_Framework_TestCase
{
public function testDoesFilterHttpData()
{
$data = array(
'sentry.interfaces.Http' => 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['sentry.interfaces.Http']['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 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(
'sentry.interfaces.Http' => 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['sentry.interfaces.Http']['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)
);
}
}

View File

@ -0,0 +1,48 @@
<?php
/*
* This file is part of Raven.
*
* (c) Sentry Team
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Raven_SerializerTestObject
{
private $foo = 'bar';
}
class Raven_Tests_SerializerTest extends PHPUnit_Framework_TestCase
{
public function testArraysAreArrays()
{
$input = array(1, 2, 3);
$result = Raven_Serializer::serialize($input);
$this->assertEquals(array('1', '2', '3'), $result);
}
public function testObjectsAreStrings()
{
$input = new Raven_StacktraceTestObject();
$result = Raven_Serializer::serialize($input);
$this->assertEquals('Object Raven_StacktraceTestObject', $result);
}
public function testIntsAreInts()
{
$input = 1;
$result = Raven_Serializer::serialize($input);
$this->assertTrue(is_integer($result));
$this->assertEquals(1, $result);
}
public function testRecursionMaxDepth()
{
$input = array();
$input[] = &$input;
$result = Raven_Serializer::serialize($input, 3);
$this->assertEquals(array(array(array('Array of length 1'))), $result);
}
}

View File

@ -0,0 +1,223 @@
<?php
/*
* This file is part of Raven.
*
* (c) Sentry Team
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
function raven_test_recurse($times, $callback)
{
$times -= 1;
if ($times > 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 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']);
}
}

View File

@ -0,0 +1,32 @@
<?php
/*
* This file is part of Raven.
*
* (c) Sentry Team
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Raven_StacktraceTestObject
{
private $foo = 'bar';
}
class Raven_Tests_UtilTest extends PHPUnit_Framework_TestCase
{
public function testGetReturnsDefaultOnMissing()
{
$input = array('foo' => '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);
}
}

View File

@ -0,0 +1,11 @@
<?php
// filename: /tmp/a.php
function a_test($str)
{
echo "\nHi: $str";
var_dump(debug_backtrace());
}
$foo = 'friend';
a_test($foo);

View File

@ -0,0 +1,3 @@
<?php
// filename: /tmp/b.php
include_once '/tmp/a.php';

View File

@ -0,0 +1,17 @@
<?php
/*
* This file is part of Raven.
*
* (c) Sentry Team
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
error_reporting(E_ALL | E_STRICT);
session_start();
require_once dirname(__FILE__).'/../lib/Raven/Autoloader.php';
Raven_Autoloader::register();