12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541 |
- <?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 = '1.9.x-dev';
-
- 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 $breadcrumbs;
- /**
- * @var Raven_Context
- */
- public $context;
- public $extra_data;
- /**
- * @var array|null
- */
- public $severity_map;
- public $store_errors_for_bulk_send = false;
-
- protected $error_handler;
- protected $error_types;
-
- /**
- * @var Raven_Serializer
- */
- protected $serializer;
- /**
- * @var Raven_ReprSerializer
- */
- protected $reprSerializer;
-
- /**
- * @var string
- */
- protected $app_path;
- /**
- * @var string[]
- */
- protected $prefixes;
- /**
- * @var string[]|null
- */
- protected $excluded_app_paths;
- /**
- * @var Callable
- */
- protected $transport;
-
- public $logger;
- /**
- * @var string Full URL to Sentry
- */
- public $server;
- public $secret_key;
- public $public_key;
- public $project;
- public $auto_log_stacks;
- public $name;
- public $site;
- public $tags;
- public $release;
- public $environment;
- public $sample_rate;
- public $trace;
- public $timeout;
- public $message_limit;
- public $exclude;
- public $excluded_exceptions;
- public $http_proxy;
- protected $send_callback;
- public $curl_method;
- public $curl_path;
- public $curl_ipv4;
- public $ca_cert;
- public $verify_ssl;
- public $curl_ssl_version;
- public $trust_x_forwarded_proto;
- public $mb_detect_order;
- /**
- * @var Raven_Processor[]
- */
- public $processors;
- /**
- * @var string|int|null
- */
- public $_lasterror;
- /**
- * @var object|null
- */
- protected $_last_sentry_error;
- public $_last_event_id;
- public $_user;
- public $_pending_events;
- public $sdk;
- /**
- * @var Raven_CurlHandler
- */
- protected $_curl_handler;
- /**
- * @var resource|null
- */
- protected $_curl_instance;
- /**
- * @var bool
- */
- protected $_shutdown_function_has_been_set;
-
- /**
- * @var bool
- */
- public $useCompression;
-
- public function __construct($options_or_dsn = null, $options = array())
- {
- if (is_array($options_or_dsn)) {
- $options = array_merge($options_or_dsn, $options);
- }
-
- if (!is_array($options_or_dsn) && !empty($options_or_dsn)) {
- $dsn = $options_or_dsn;
- } elseif (!empty($_SERVER['SENTRY_DSN'])) {
- $dsn = @$_SERVER['SENTRY_DSN'];
- } elseif (!empty($options['dsn'])) {
- $dsn = $options['dsn'];
- } else {
- $dsn = null;
- }
-
- if (!empty($dsn)) {
- $options = array_merge($options, self::parseDSN($dsn));
- }
-
- $this->logger = Raven_Util::get($options, 'logger', 'php');
- $this->server = Raven_Util::get($options, 'server');
- $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', self::_server_variable('SERVER_NAME'));
- $this->tags = Raven_Util::get($options, 'tags', array());
- $this->release = Raven_Util::get($options, 'release', null);
- $this->environment = Raven_Util::get($options, 'environment', null);
- $this->sample_rate = Raven_Util::get($options, 'sample_rate', 1);
- $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->excluded_exceptions = Raven_Util::get($options, 'excluded_exceptions', array());
- $this->severity_map = null;
- $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', static::get_default_ca_cert());
- $this->verify_ssl = Raven_Util::get($options, 'verify_ssl', true);
- $this->curl_ssl_version = Raven_Util::get($options, 'curl_ssl_version');
- $this->trust_x_forwarded_proto = Raven_Util::get($options, 'trust_x_forwarded_proto');
- $this->transport = Raven_Util::get($options, 'transport', null);
- $this->mb_detect_order = Raven_Util::get($options, 'mb_detect_order', null);
- $this->error_types = Raven_Util::get($options, 'error_types', null);
-
- // app path is used to determine if code is part of your application
- $this->setAppPath(Raven_Util::get($options, 'app_path', null));
- $this->setExcludedAppPaths(Raven_Util::get($options, 'excluded_app_paths', null));
- // a list of prefixes used to coerce absolute paths into relative
- $this->setPrefixes(Raven_Util::get($options, 'prefixes', static::getDefaultPrefixes()));
- $this->processors = $this->setProcessorsFromOptions($options);
-
- $this->_lasterror = null;
- $this->_last_sentry_error = null;
- $this->_curl_instance = null;
- $this->_last_event_id = null;
- $this->_user = null;
- $this->_pending_events = array();
- $this->context = new Raven_Context();
- $this->breadcrumbs = new Raven_Breadcrumbs();
- $this->_shutdown_function_has_been_set = false;
- $this->useCompression = function_exists('gzcompress');
-
- $this->sdk = Raven_Util::get($options, 'sdk', array(
- 'name' => 'sentry-php',
- 'version' => self::VERSION,
- ));
- $this->serializer = new Raven_Serializer($this->mb_detect_order);
- $this->reprSerializer = new Raven_ReprSerializer($this->mb_detect_order);
-
- if ($this->curl_method == 'async') {
- $this->_curl_handler = new Raven_CurlHandler($this->get_curl_options());
- }
-
- $this->transaction = new Raven_TransactionStack();
- if (static::is_http_request() && isset($_SERVER['PATH_INFO'])) {
- // @codeCoverageIgnoreStart
- $this->transaction->push($_SERVER['PATH_INFO']);
- // @codeCoverageIgnoreEnd
- }
-
- if (Raven_Util::get($options, 'install_default_breadcrumb_handlers', true)) {
- $this->registerDefaultBreadcrumbHandlers();
- }
-
- if (Raven_Util::get($options, 'install_shutdown_handler', true)) {
- $this->registerShutdownFunction();
- }
-
- $this->triggerAutoload();
- }
-
- public function __destruct()
- {
- // Force close curl resource
- $this->close_curl_resource();
- }
-
- /**
- * Destruct all objects contain link to this object
- *
- * This method can not delete shutdown handler
- */
- public function close_all_children_link()
- {
- $this->processors = array();
- }
-
- /**
- * Installs any available automated hooks (such as error_reporting).
- */
- public function install()
- {
- if ($this->error_handler) {
- throw new Raven_Exception(sprintf('%s->install() must only be called once', get_class($this)));
- }
- $this->error_handler = new Raven_ErrorHandler($this, false, $this->error_types);
- $this->error_handler->registerExceptionHandler();
- $this->error_handler->registerErrorHandler();
- $this->error_handler->registerShutdownFunction();
-
- if ($this->_curl_handler) {
- $this->_curl_handler->registerShutdownFunction();
- }
-
- return $this;
- }
-
- public function getRelease()
- {
- return $this->release;
- }
-
- public function setRelease($value)
- {
- $this->release = $value;
- return $this;
- }
-
- public function getEnvironment()
- {
- return $this->environment;
- }
-
- public function setEnvironment($value)
- {
- $this->environment = $value;
- return $this;
- }
-
- /**
- * Note: Prior to PHP 5.6, a stream opened with php://input can
- * only be read once;
- *
- * @see http://php.net/manual/en/wrappers.php.php
- */
- protected static function getInputStream()
- {
- if (PHP_VERSION_ID < 50600) {
- return null;
- }
-
- return file_get_contents('php://input');
- }
-
- private static function getDefaultPrefixes()
- {
- $value = get_include_path();
- return explode(PATH_SEPARATOR, $value);
- }
-
- private static function _convertPath($value)
- {
- $path = @realpath($value);
- if ($path === false) {
- $path = $value;
- }
- // we need app_path to have a trailing slash otherwise
- // base path detection becomes complex if the same
- // prefix is matched
- if ($path{0} === DIRECTORY_SEPARATOR && substr($path, -1) !== DIRECTORY_SEPARATOR) {
- $path .= DIRECTORY_SEPARATOR;
- }
- return $path;
- }
-
- public function getAppPath()
- {
- return $this->app_path;
- }
-
- public function setAppPath($value)
- {
- if ($value) {
- $this->app_path = static::_convertPath($value);
- } else {
- $this->app_path = null;
- }
- return $this;
- }
-
- public function getExcludedAppPaths()
- {
- return $this->excluded_app_paths;
- }
-
- public function setExcludedAppPaths($value)
- {
- if ($value) {
- $excluded_app_paths = array();
-
- // We should be able to exclude a php files
- foreach ((array) $value as $path) {
- $excluded_app_paths[] = substr($path, -4) !== '.php' ? self::_convertPath($path) : $path;
- }
- } else {
- $excluded_app_paths = null;
- }
-
- $this->excluded_app_paths = $excluded_app_paths;
-
- return $this;
- }
-
- public function getPrefixes()
- {
- return $this->prefixes;
- }
-
- /**
- * @param array $value
- * @return Raven_Client
- */
- public function setPrefixes($value)
- {
- $this->prefixes = $value ? array_map(array($this, '_convertPath'), $value) : $value;
- return $this;
- }
-
- public function getSendCallback()
- {
- return $this->send_callback;
- }
-
- public function setSendCallback($value)
- {
- $this->send_callback = $value;
- return $this;
- }
-
- public function getTransport()
- {
- return $this->transport;
- }
-
- public function getServerEndpoint($value = '')
- {
- return $this->server;
- }
-
- public static function getUserAgent()
- {
- return 'sentry-php/' . self::VERSION;
- }
-
- /**
- * Set a custom transport to override how Sentry events are sent upstream.
- *
- * The bound function will be called with ``$client`` and ``$data`` arguments
- * and is responsible for encoding the data, authenticating, and sending
- * the data to the upstream Sentry server.
- *
- * @param Callable $value Function to be called
- * @return Raven_Client
- */
- public function setTransport($value)
- {
- $this->transport = $value;
- return $this;
- }
-
- /**
- * @return string[]|Raven_Processor[]
- */
- public static function getDefaultProcessors()
- {
- return array(
- 'Raven_Processor_SanitizeDataProcessor',
- );
- }
-
- /**
- * Sets the Raven_Processor sub-classes to be used when data is processed before being
- * sent to Sentry.
- *
- * @param $options
- * @return Raven_Processor[]
- */
- public function setProcessorsFromOptions($options)
- {
- $processors = array();
- foreach (Raven_util::get($options, 'processors', static::getDefaultProcessors()) as $processor) {
- /**
- * @var Raven_Processor $new_processor
- * @var Raven_Processor|string $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
- * @return array parsed DSN
- *
- * @see http://raven.readthedocs.org/en/latest/config/#the-sentry-dsn
- */
- public static function parseDSN($dsn)
- {
- switch (strtolower($dsn)) {
- case '':
- case 'false':
- case '(false)':
- case 'empty':
- case '(empty)':
- case 'null':
- case '(null)':
- return array();
- }
-
- $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(
- 'server' => 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.
- *
- * @param mixed $ident
- * @return mixed
- * @codeCoverageIgnore
- */
- public function getIdent($ident)
- {
- // XXX: We don't calculate checksums yet, so we only have the ident.
- return $ident;
- }
-
- /**
- * @param string $message The message (primary description) for the event.
- * @param array $params params to use when formatting the message.
- * @param string $level Log level group
- * @param bool|array $stack
- * @param mixed $vars
- * @return string|null
- * @deprecated
- * @codeCoverageIgnore
- */
- public function message($message, $params = array(), $level = self::INFO,
- $stack = false, $vars = null)
- {
- return $this->captureMessage($message, $params, $level, $stack, $vars);
- }
-
- /**
- * @param Exception $exception
- * @return string|null
- * @deprecated
- * @codeCoverageIgnore
- */
- public function exception($exception)
- {
- return $this->captureException($exception);
- }
-
- /**
- * Log a message to sentry
- *
- * @param string $message The message (primary description) for the event.
- * @param array $params params to use when formatting the message.
- * @param array $data Additional attributes to pass with this event (see Sentry docs).
- * @param bool|array $stack
- * @param mixed $vars
- * @return string|null
- */
- public function captureMessage($message, $params = array(), $data = 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 ($data === null) {
- $data = array();
- // support legacy method of passing in a level name as the third arg
- } elseif (!is_array($data)) {
- $data = array(
- 'level' => $data,
- );
- }
-
- $data['message'] = $formatted_message;
- $data['sentry.interfaces.Message'] = array(
- 'message' => $message,
- 'params' => $params,
- 'formatted' => $formatted_message,
- );
-
- return $this->capture($data, $stack, $vars);
- }
-
- /**
- * Log an exception to sentry
- *
- * @param \Throwable|\Exception $exception The Throwable/Exception object.
- * @param array $data Additional attributes to pass with this event (see Sentry docs).
- * @param mixed $logger
- * @param mixed $vars
- * @return string|null
- */
- public function captureException($exception, $data = null, $logger = null, $vars = null)
- {
- $has_chained_exceptions = PHP_VERSION_ID >= 50300;
-
- if (in_array(get_class($exception), $this->exclude)) {
- return null;
- }
-
- foreach ($this->excluded_exceptions as $exclude) {
- if ($exception instanceof $exclude) {
- return null;
- }
- }
-
- if ($data === null) {
- $data = array();
- }
-
- $exc = $exception;
- do {
- $exc_data = array(
- 'value' => $this->serializer->serialize($exc->getMessage()),
- 'type' => get_class($exc),
- );
-
- /**'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);
-
- $exc_data['stacktrace'] = array(
- 'frames' => Raven_Stacktrace::get_stack_info(
- $trace, $this->trace, $vars, $this->message_limit, $this->prefixes,
- $this->app_path, $this->excluded_app_paths, $this->serializer, $this->reprSerializer
- ),
- );
-
- $exceptions[] = $exc_data;
- } while ($has_chained_exceptions && $exc = $exc->getPrevious());
-
- $data['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);
- }
-
-
- /**
- * Capture the most recent error (obtained with ``error_get_last``).
- * @return string|null
- */
- public function captureLastError()
- {
- if (null === $error = error_get_last()) {
- return null;
- }
-
- $e = new ErrorException(
- @$error['message'], 0, @$error['type'],
- @$error['file'], @$error['line']
- );
-
- return $this->captureException($e);
- }
-
- /**
- * Log an query to sentry
- *
- * @param string|null $query
- * @param string $level
- * @param string $engine
- */
- 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);
- }
-
- /**
- * Return the last captured event's ID or null if none available.
- */
- public function getLastEventID()
- {
- return $this->_last_event_id;
- }
-
- protected function registerDefaultBreadcrumbHandlers()
- {
- $handler = new Raven_Breadcrumbs_ErrorHandler($this);
- $handler->install();
- }
-
- protected function registerShutdownFunction()
- {
- if (!$this->_shutdown_function_has_been_set) {
- $this->_shutdown_function_has_been_set = true;
- register_shutdown_function(array($this, 'onShutdown'));
- }
- }
-
- /**
- * @return bool
- * @codeCoverageIgnore
- */
- protected static function is_http_request()
- {
- return isset($_SERVER['REQUEST_METHOD']) && PHP_SAPI !== 'cli';
- }
-
- protected function get_http_data()
- {
- $headers = array();
-
- foreach ($_SERVER as $key => $value) {
- if (0 === strpos($key, 'HTTP_')) {
- $header_key =
- str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))));
- $headers[$header_key] = $value;
- } elseif (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH')) && $value !== '') {
- $header_key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key))));
- $headers[$header_key] = $value;
- }
- }
-
- $result = array(
- 'method' => self::_server_variable('REQUEST_METHOD'),
- 'url' => $this->get_current_url(),
- 'query_string' => self::_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;
- } elseif (isset($_SERVER['CONTENT_TYPE']) && stripos($_SERVER['CONTENT_TYPE'], 'application/json') === 0) {
- $raw_data = $this->getInputStream() ?: false;
- if ($raw_data !== false) {
- $result['data'] = (array) json_decode($raw_data, true) ?: null;
- }
- }
- if (!empty($_COOKIE)) {
- $result['cookies'] = $_COOKIE;
- }
- if (!empty($headers)) {
- $result['headers'] = $headers;
- }
-
- return array(
- 'request' => $result,
- );
- }
-
- protected function get_user_data()
- {
- $user = $this->context->user;
- if ($user === null) {
- if (!function_exists('session_id') || !session_id()) {
- return array();
- }
- $user = array(
- 'id' => session_id(),
- );
- if (!empty($_SERVER['REMOTE_ADDR'])) {
- $user['ip_address'] = $_SERVER['REMOTE_ADDR'];
- }
- if (!empty($_SESSION)) {
- $user['data'] = $_SESSION;
- }
- }
- return array(
- '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',
- 'sdk' => $this->sdk,
- 'culprit' => $this->transaction->peek(),
- );
- }
-
- public function capture($data, $stack = null, $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'] = static::uuid4();
- }
-
- if (isset($data['message'])) {
- $data['message'] = substr($data['message'], 0, $this->message_limit);
- }
-
- $data = array_merge($this->get_default_data(), $data);
-
- if (static::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;
- }
- if ($this->environment) {
- $data['environment'] = $this->environment;
- }
-
- $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 (empty($data['extra'])) {
- unset($data['extra']);
- }
- if (empty($data['tags'])) {
- unset($data['tags']);
- }
- if (empty($data['user'])) {
- unset($data['user']);
- }
- if (empty($data['request'])) {
- unset($data['request']);
- }
- if (empty($data['site'])) {
- unset($data['site']);
- }
-
- $existing_runtime_context = isset($data['contexts']['runtime']) ? $data['contexts']['runtime'] : array();
- $runtime_context = array('version' => PHP_VERSION, 'name' => 'php');
- $data['contexts']['runtime'] = array_merge($runtime_context, $existing_runtime_context);
-
- if (!$this->breadcrumbs->is_empty()) {
- $data['breadcrumbs'] = $this->breadcrumbs->fetch();
- }
-
- if ((!$stack && $this->auto_log_stacks) || $stack === true) {
- $stack = debug_backtrace();
-
- // Drop last stack
- array_shift($stack);
- }
-
- if (! empty($stack) && ! isset($data['stacktrace']) && ! isset($data['exception'])) {
- $data['stacktrace'] = array(
- 'frames' => Raven_Stacktrace::get_stack_info(
- $stack, $this->trace, $vars, $this->message_limit, $this->prefixes,
- $this->app_path, $this->excluded_app_paths, $this->serializer, $this->reprSerializer
- ),
- );
- }
-
- $this->sanitize($data);
- $this->process($data);
-
- if (!$this->store_errors_for_bulk_send) {
- $this->send($data);
- } else {
- $this->_pending_events[] = $data;
- }
-
- $this->_last_event_id = $data['event_id'];
-
- return $data['event_id'];
- }
-
- public function sanitize(&$data)
- {
- // attempt to sanitize any user provided data
- if (!empty($data['request'])) {
- $data['request'] = $this->serializer->serialize($data['request'], 5);
- }
- if (!empty($data['user'])) {
- $data['user'] = $this->serializer->serialize($data['user'], 3);
- }
- if (!empty($data['extra'])) {
- $data['extra'] = $this->serializer->serialize($data['extra']);
- }
- if (!empty($data['tags'])) {
- foreach ($data['tags'] as $key => $value) {
- $data['tags'][$key] = @(string)$value;
- }
- }
- if (!empty($data['contexts'])) {
- $data['contexts'] = $this->serializer->serialize($data['contexts'], 5);
- }
- if (!empty($data['breadcrumbs'])) {
- $data['breadcrumbs'] = $this->serializer->serialize($data['breadcrumbs'], 5);
- }
- }
-
- /**
- * 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()
- {
- foreach ($this->_pending_events as $data) {
- $this->send($data);
- }
- $this->_pending_events = array();
- if ($this->store_errors_for_bulk_send) {
- //in case an error occurs after this is called, on shutdown, send any new errors.
- $this->store_errors_for_bulk_send = !defined('RAVEN_CLIENT_END_REACHED');
- }
- }
-
- /**
- * @param array $data
- * @return string|bool
- */
- public function encode(&$data)
- {
- $message = Raven_Compat::json_encode($data);
- if ($message === false) {
- if (function_exists('json_last_error_msg')) {
- $this->_lasterror = json_last_error_msg();
- } else {
- // @codeCoverageIgnoreStart
- $this->_lasterror = json_last_error();
- // @codeCoverageIgnoreEnd
- }
- return false;
- }
-
- if ($this->useCompression) {
- $message = gzcompress($message);
- }
-
- // PHP's builtin curl_* function are happy without this, but the exec method requires it
- $message = base64_encode($message);
-
- return $message;
- }
-
- /**
- * Wrapper to handle encoding and sending data to the Sentry API server.
- *
- * @param array $data Associative array of data to log
- */
- public function send(&$data)
- {
- if (is_callable($this->send_callback)
- && call_user_func_array($this->send_callback, array(&$data)) === false
- ) {
- // if send_callback returns false, end native send
- return;
- }
-
- if (!$this->server) {
- return;
- }
-
- if ($this->transport) {
- call_user_func($this->transport, $this, $data);
- return;
- }
-
- // should this event be sampled?
- if (rand(1, 100) / 100.0 > $this->sample_rate) {
- return;
- }
-
- $message = $this->encode($data);
-
- $headers = array(
- 'User-Agent' => static::getUserAgent(),
- 'X-Sentry-Auth' => $this->getAuthHeader(),
- 'Content-Type' => 'application/octet-stream'
- );
-
- $this->send_remote($this->server, $message, $headers);
- }
-
- /**
- * Send data to Sentry
- *
- * @param string $url Full URL to Sentry
- * @param array|string $data Associative array of data to log
- * @param array $headers Associative array of headers
- */
- protected 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 static function get_default_ca_cert()
- {
- return dirname(__FILE__) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cacert.pem';
- }
-
- /**
- * @return array
- * @see http://stackoverflow.com/questions/9062798/php-curl-timeout-is-not-working/9063006#9063006
- */
- 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 => 'sentry-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 stackoverflow link in the phpdoc
- 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|string $data Associative array of data to log
- * @param array $headers Associative array of headers
- */
- protected 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);
- }
- }
-
- protected function buildCurlCommand($url, $data, $headers)
- {
- // TODO(dcramer): support ca_cert
- $cmd = $this->curl_path.' -X POST ';
- foreach ($headers as $key => $value) {
- $cmd .= '-H ' . escapeshellarg($key.': '.$value). ' ';
- }
- $cmd .= '-d ' . escapeshellarg($data) . ' ';
- $cmd .= escapeshellarg($url) . ' ';
- $cmd .= '-m 5 '; // 5 second timeout for the whole process (connect + send)
- if (!$this->verify_ssl) {
- $cmd .= '-k ';
- }
- $cmd .= '> /dev/null 2>&1 &'; // ensure exec returns immediately while curl runs in the background
-
- return $cmd;
- }
-
- /**
- * Send the cURL to Sentry asynchronously. No errors will be returned from cURL
- *
- * @param string $url URL of the Sentry instance to log to
- * @param array|string $data Associative array of data to log
- * @param array $headers Associative array of headers
- * @return bool
- */
- protected function send_http_asynchronous_curl_exec($url, $data, $headers)
- {
- exec($this->buildCurlCommand($url, $data, $headers));
- return true; // The exec method is just fire and forget, so just assume it always works
- }
-
- /**
- * 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|string $data Associative array of data to log
- * @param array $headers Associative array of headers
- * @return bool
- */
- protected 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:';
-
- if (is_null($this->_curl_instance)) {
- $this->_curl_instance = curl_init($url);
- }
- curl_setopt($this->_curl_instance, CURLOPT_POST, 1);
- curl_setopt($this->_curl_instance, CURLOPT_HTTPHEADER, $new_headers);
- curl_setopt($this->_curl_instance, CURLOPT_POSTFIELDS, $data);
- curl_setopt($this->_curl_instance, CURLOPT_RETURNTRANSFER, true);
-
- $options = $this->get_curl_options();
- if (isset($options[CURLOPT_CAINFO])) {
- $ca_cert = $options[CURLOPT_CAINFO];
- unset($options[CURLOPT_CAINFO]);
- } else {
- $ca_cert = null;
- }
- curl_setopt_array($this->_curl_instance, $options);
-
- $buffer = curl_exec($this->_curl_instance);
-
- $errno = curl_errno($this->_curl_instance);
- // CURLE_SSL_CACERT || CURLE_SSL_CACERT_BADFILE
- if ((($errno == 60) || ($errno == 77)) && !is_null($ca_cert)) {
- curl_setopt($this->_curl_instance, CURLOPT_CAINFO, $ca_cert);
- $buffer = curl_exec($this->_curl_instance);
- }
- if ($errno != 0) {
- $this->_lasterror = curl_error($this->_curl_instance);
- $this->_last_sentry_error = null;
- return false;
- }
-
- $code = curl_getinfo($this->_curl_instance, CURLINFO_HTTP_CODE);
- $success = ($code == 200);
- if ($success) {
- $this->_lasterror = null;
- $this->_last_sentry_error = null;
- } else {
- // It'd be nice just to raise an exception here, but it's not very PHP-like
- $this->_lasterror = curl_error($this->_curl_instance);
- $this->_last_sentry_error = @json_decode($buffer);
- }
-
- 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 static 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));
- }
-
- public function getAuthHeader()
- {
- $timestamp = microtime(true);
- return $this->get_auth_header(
- $timestamp, static::getUserAgent(), $this->public_key, $this->secret_key
- );
- }
-
- /**
- * Generate an uuid4 value
- *
- * @return string
- */
- protected static 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
- */
- protected function get_current_url()
- {
- // When running from commandline the REQUEST_URI is missing.
- if (!isset($_SERVER['REQUEST_URI'])) {
- return null;
- }
-
- // 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'] : '')));
-
- $hasNonDefaultPort = !empty($_SERVER['SERVER_PORT']) && !in_array((int)$_SERVER['SERVER_PORT'], array(80, 443));
- if ($hasNonDefaultPort && !preg_match('#:[0-9]*$#', $host)) {
- $host .= ':' . $_SERVER['SERVER_PORT'];
- }
-
- $httpS = $this->isHttps() ? 's' : '';
- return "http{$httpS}://{$host}{$_SERVER['REQUEST_URI']}";
- }
-
- /**
- * Was the current request made over https?
- *
- * @return bool
- */
- protected function isHttps()
- {
- if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
- return true;
- }
-
- if (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) {
- return true;
- }
-
- if (!empty($this->trust_x_forwarded_proto) &&
- !empty($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
- $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
- return true;
- }
-
- return false;
- }
-
- /**
- * Get the value of a key from $_SERVER
- *
- * @param string $key Key whose value you wish to obtain
- * @return string Key's value
- */
- private static 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 (PHP_VERSION_ID >= 50300) {
- 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
- *
- * @deprecated
- * @param string $id User's ID
- * @param string|null $email User's email
- * @param array $data Additional user data
- * @codeCoverageIgnore
- */
- public function set_user_data($id, $email = null, $data = array())
- {
- $user = array('id' => $id);
- if (isset($email)) {
- $user['email'] = $email;
- }
- $this->user_context(array_merge($user, $data));
- }
-
- public function onShutdown()
- {
- if (!defined('RAVEN_CLIENT_END_REACHED')) {
- define('RAVEN_CLIENT_END_REACHED', true);
- }
- $this->sendUnsentErrors();
- if ($this->curl_method == 'async') {
- $this->_curl_handler->join();
- }
- }
-
- /**
- * Sets user context.
- *
- * @param array $data Associative array of user data
- * @param bool $merge Merge existing context with new context
- */
- public function user_context($data, $merge = true)
- {
- if ($merge && $this->context->user !== null) {
- // bail if data is null
- if (!$data) {
- return;
- }
- $this->context->user = array_merge($this->context->user, $data);
- } else {
- $this->context->user = $data;
- }
- }
-
- /**
- * 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;
- }
-
- /**
- * @return object|null
- */
- public function getLastSentryError()
- {
- return $this->_last_sentry_error;
- }
-
- /**
- * @return bool
- */
- public function getShutdownFunctionHasBeenSet()
- {
- return $this->_shutdown_function_has_been_set;
- }
-
- public function close_curl_resource()
- {
- if (!is_null($this->_curl_instance)) {
- curl_close($this->_curl_instance);
- $this->_curl_instance = null;
- }
- }
-
- /**
- * @param Raven_Serializer $serializer
- */
- public function setSerializer(Raven_Serializer $serializer)
- {
- $this->serializer = $serializer;
- }
-
- /**
- * @param Raven_ReprSerializer $reprSerializer
- */
- public function setReprSerializer(Raven_ReprSerializer $reprSerializer)
- {
- $this->reprSerializer = $reprSerializer;
- }
-
- private function triggerAutoload()
- {
- // manually trigger autoloading, as it cannot be done during error handling in some edge cases due to PHP (see #60149)
-
- if (! class_exists('Raven_Stacktrace')) {
- spl_autoload_call('Raven_Stacktrace');
- }
-
- if (function_exists('mb_detect_encoding')) {
- mb_detect_encoding('string');
- }
-
- if (function_exists('mb_convert_encoding')) {
- mb_convert_encoding('string', 'UTF8');
- }
- }
- }
|