Client.php 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540
  1. <?php
  2. /*
  3. * This file is part of Raven.
  4. *
  5. * (c) Sentry Team
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. /**
  11. * Raven PHP Client
  12. *
  13. * @package raven
  14. */
  15. class Raven_Client
  16. {
  17. const VERSION = '1.9.x-dev';
  18. const PROTOCOL = '6';
  19. const DEBUG = 'debug';
  20. const INFO = 'info';
  21. const WARN = 'warning';
  22. const WARNING = 'warning';
  23. const ERROR = 'error';
  24. const FATAL = 'fatal';
  25. const MESSAGE_LIMIT = 1024;
  26. public $breadcrumbs;
  27. /**
  28. * @var Raven_Context
  29. */
  30. public $context;
  31. public $extra_data;
  32. /**
  33. * @var array|null
  34. */
  35. public $severity_map;
  36. public $store_errors_for_bulk_send = false;
  37. protected $error_handler;
  38. protected $error_types;
  39. /**
  40. * @var Raven_Serializer
  41. */
  42. protected $serializer;
  43. /**
  44. * @var Raven_ReprSerializer
  45. */
  46. protected $reprSerializer;
  47. /**
  48. * @var string
  49. */
  50. protected $app_path;
  51. /**
  52. * @var string[]
  53. */
  54. protected $prefixes;
  55. /**
  56. * @var string[]|null
  57. */
  58. protected $excluded_app_paths;
  59. /**
  60. * @var Callable
  61. */
  62. protected $transport;
  63. public $logger;
  64. /**
  65. * @var string Full URL to Sentry
  66. */
  67. public $server;
  68. public $secret_key;
  69. public $public_key;
  70. public $project;
  71. public $auto_log_stacks;
  72. public $name;
  73. public $site;
  74. public $tags;
  75. public $release;
  76. public $environment;
  77. public $sample_rate;
  78. public $trace;
  79. public $timeout;
  80. public $message_limit;
  81. public $exclude;
  82. public $excluded_exceptions;
  83. public $http_proxy;
  84. protected $send_callback;
  85. public $curl_method;
  86. public $curl_path;
  87. public $curl_ipv4;
  88. public $ca_cert;
  89. public $verify_ssl;
  90. public $curl_ssl_version;
  91. public $trust_x_forwarded_proto;
  92. public $mb_detect_order;
  93. /**
  94. * @var Raven_Processor[]
  95. */
  96. public $processors;
  97. /**
  98. * @var string|int|null
  99. */
  100. public $_lasterror;
  101. /**
  102. * @var object|null
  103. */
  104. protected $_last_sentry_error;
  105. public $_last_event_id;
  106. public $_user;
  107. public $_pending_events;
  108. public $sdk;
  109. /**
  110. * @var Raven_CurlHandler
  111. */
  112. protected $_curl_handler;
  113. /**
  114. * @var resource|null
  115. */
  116. protected $_curl_instance;
  117. /**
  118. * @var bool
  119. */
  120. protected $_shutdown_function_has_been_set;
  121. /**
  122. * @var bool
  123. */
  124. public $useCompression;
  125. public function __construct($options_or_dsn = null, $options = array())
  126. {
  127. if (is_array($options_or_dsn)) {
  128. $options = array_merge($options_or_dsn, $options);
  129. }
  130. if (!is_array($options_or_dsn) && !empty($options_or_dsn)) {
  131. $dsn = $options_or_dsn;
  132. } elseif (!empty($_SERVER['SENTRY_DSN'])) {
  133. $dsn = @$_SERVER['SENTRY_DSN'];
  134. } elseif (!empty($options['dsn'])) {
  135. $dsn = $options['dsn'];
  136. } else {
  137. $dsn = null;
  138. }
  139. if (!empty($dsn)) {
  140. $options = array_merge($options, self::parseDSN($dsn));
  141. }
  142. $this->logger = Raven_Util::get($options, 'logger', 'php');
  143. $this->server = Raven_Util::get($options, 'server');
  144. $this->secret_key = Raven_Util::get($options, 'secret_key');
  145. $this->public_key = Raven_Util::get($options, 'public_key');
  146. $this->project = Raven_Util::get($options, 'project', 1);
  147. $this->auto_log_stacks = (bool) Raven_Util::get($options, 'auto_log_stacks', false);
  148. $this->name = Raven_Util::get($options, 'name', Raven_Compat::gethostname());
  149. $this->site = Raven_Util::get($options, 'site', self::_server_variable('SERVER_NAME'));
  150. $this->tags = Raven_Util::get($options, 'tags', array());
  151. $this->release = Raven_Util::get($options, 'release', null);
  152. $this->environment = Raven_Util::get($options, 'environment', null);
  153. $this->sample_rate = Raven_Util::get($options, 'sample_rate', 1);
  154. $this->trace = (bool) Raven_Util::get($options, 'trace', true);
  155. $this->timeout = Raven_Util::get($options, 'timeout', 2);
  156. $this->message_limit = Raven_Util::get($options, 'message_limit', self::MESSAGE_LIMIT);
  157. $this->exclude = Raven_Util::get($options, 'exclude', array());
  158. $this->excluded_exceptions = Raven_Util::get($options, 'excluded_exceptions', array());
  159. $this->severity_map = null;
  160. $this->http_proxy = Raven_Util::get($options, 'http_proxy');
  161. $this->extra_data = Raven_Util::get($options, 'extra', array());
  162. $this->send_callback = Raven_Util::get($options, 'send_callback', null);
  163. $this->curl_method = Raven_Util::get($options, 'curl_method', 'sync');
  164. $this->curl_path = Raven_Util::get($options, 'curl_path', 'curl');
  165. $this->curl_ipv4 = Raven_Util::get($options, 'curl_ipv4', true);
  166. $this->ca_cert = Raven_Util::get($options, 'ca_cert', static::get_default_ca_cert());
  167. $this->verify_ssl = Raven_Util::get($options, 'verify_ssl', true);
  168. $this->curl_ssl_version = Raven_Util::get($options, 'curl_ssl_version');
  169. $this->trust_x_forwarded_proto = Raven_Util::get($options, 'trust_x_forwarded_proto');
  170. $this->transport = Raven_Util::get($options, 'transport', null);
  171. $this->mb_detect_order = Raven_Util::get($options, 'mb_detect_order', null);
  172. $this->error_types = Raven_Util::get($options, 'error_types', null);
  173. // app path is used to determine if code is part of your application
  174. $this->setAppPath(Raven_Util::get($options, 'app_path', null));
  175. $this->setExcludedAppPaths(Raven_Util::get($options, 'excluded_app_paths', null));
  176. // a list of prefixes used to coerce absolute paths into relative
  177. $this->setPrefixes(Raven_Util::get($options, 'prefixes', static::getDefaultPrefixes()));
  178. $this->processors = $this->setProcessorsFromOptions($options);
  179. $this->_lasterror = null;
  180. $this->_last_sentry_error = null;
  181. $this->_curl_instance = null;
  182. $this->_last_event_id = null;
  183. $this->_user = null;
  184. $this->_pending_events = array();
  185. $this->context = new Raven_Context();
  186. $this->breadcrumbs = new Raven_Breadcrumbs();
  187. $this->_shutdown_function_has_been_set = false;
  188. $this->useCompression = function_exists('gzcompress');
  189. $this->sdk = Raven_Util::get($options, 'sdk', array(
  190. 'name' => 'sentry-php',
  191. 'version' => self::VERSION,
  192. ));
  193. $this->serializer = new Raven_Serializer($this->mb_detect_order);
  194. $this->reprSerializer = new Raven_ReprSerializer($this->mb_detect_order);
  195. if ($this->curl_method == 'async') {
  196. $this->_curl_handler = new Raven_CurlHandler($this->get_curl_options());
  197. }
  198. $this->transaction = new Raven_TransactionStack();
  199. if (static::is_http_request() && isset($_SERVER['PATH_INFO'])) {
  200. // @codeCoverageIgnoreStart
  201. $this->transaction->push($_SERVER['PATH_INFO']);
  202. // @codeCoverageIgnoreEnd
  203. }
  204. if (Raven_Util::get($options, 'install_default_breadcrumb_handlers', true)) {
  205. $this->registerDefaultBreadcrumbHandlers();
  206. }
  207. if (Raven_Util::get($options, 'install_shutdown_handler', true)) {
  208. $this->registerShutdownFunction();
  209. }
  210. $this->triggerAutoload();
  211. }
  212. public function __destruct()
  213. {
  214. // Force close curl resource
  215. $this->close_curl_resource();
  216. }
  217. /**
  218. * Destruct all objects contain link to this object
  219. *
  220. * This method can not delete shutdown handler
  221. */
  222. public function close_all_children_link()
  223. {
  224. $this->processors = array();
  225. }
  226. /**
  227. * Installs any available automated hooks (such as error_reporting).
  228. */
  229. public function install()
  230. {
  231. if ($this->error_handler) {
  232. throw new Raven_Exception(sprintf('%s->install() must only be called once', get_class($this)));
  233. }
  234. $this->error_handler = new Raven_ErrorHandler($this, false, $this->error_types);
  235. $this->error_handler->registerExceptionHandler();
  236. $this->error_handler->registerErrorHandler();
  237. $this->error_handler->registerShutdownFunction();
  238. if ($this->_curl_handler) {
  239. $this->_curl_handler->registerShutdownFunction();
  240. }
  241. return $this;
  242. }
  243. public function getRelease()
  244. {
  245. return $this->release;
  246. }
  247. public function setRelease($value)
  248. {
  249. $this->release = $value;
  250. return $this;
  251. }
  252. public function getEnvironment()
  253. {
  254. return $this->environment;
  255. }
  256. public function setEnvironment($value)
  257. {
  258. $this->environment = $value;
  259. return $this;
  260. }
  261. /**
  262. * Note: Prior to PHP 5.6, a stream opened with php://input can
  263. * only be read once;
  264. *
  265. * @see http://php.net/manual/en/wrappers.php.php
  266. */
  267. protected static function getInputStream()
  268. {
  269. if (PHP_VERSION_ID < 50600) {
  270. return null;
  271. }
  272. return file_get_contents('php://input');
  273. }
  274. private static function getDefaultPrefixes()
  275. {
  276. $value = get_include_path();
  277. return explode(PATH_SEPARATOR, $value);
  278. }
  279. private static function _convertPath($value)
  280. {
  281. $path = @realpath($value);
  282. if ($path === false) {
  283. $path = $value;
  284. }
  285. // we need app_path to have a trailing slash otherwise
  286. // base path detection becomes complex if the same
  287. // prefix is matched
  288. if ($path{0} === DIRECTORY_SEPARATOR && substr($path, -1) !== DIRECTORY_SEPARATOR) {
  289. $path .= DIRECTORY_SEPARATOR;
  290. }
  291. return $path;
  292. }
  293. public function getAppPath()
  294. {
  295. return $this->app_path;
  296. }
  297. public function setAppPath($value)
  298. {
  299. if ($value) {
  300. $this->app_path = static::_convertPath($value);
  301. } else {
  302. $this->app_path = null;
  303. }
  304. return $this;
  305. }
  306. public function getExcludedAppPaths()
  307. {
  308. return $this->excluded_app_paths;
  309. }
  310. public function setExcludedAppPaths($value)
  311. {
  312. if ($value) {
  313. $excluded_app_paths = array();
  314. // We should be able to exclude a php files
  315. foreach ((array) $value as $path) {
  316. $excluded_app_paths[] = substr($path, -4) !== '.php' ? self::_convertPath($path) : $path;
  317. }
  318. } else {
  319. $excluded_app_paths = null;
  320. }
  321. $this->excluded_app_paths = $excluded_app_paths;
  322. return $this;
  323. }
  324. public function getPrefixes()
  325. {
  326. return $this->prefixes;
  327. }
  328. /**
  329. * @param array $value
  330. * @return Raven_Client
  331. */
  332. public function setPrefixes($value)
  333. {
  334. $this->prefixes = $value ? array_map(array($this, '_convertPath'), $value) : $value;
  335. return $this;
  336. }
  337. public function getSendCallback()
  338. {
  339. return $this->send_callback;
  340. }
  341. public function setSendCallback($value)
  342. {
  343. $this->send_callback = $value;
  344. return $this;
  345. }
  346. public function getTransport()
  347. {
  348. return $this->transport;
  349. }
  350. public function getServerEndpoint($value = '')
  351. {
  352. return $this->server;
  353. }
  354. public static function getUserAgent()
  355. {
  356. return 'sentry-php/' . self::VERSION;
  357. }
  358. /**
  359. * Set a custom transport to override how Sentry events are sent upstream.
  360. *
  361. * The bound function will be called with ``$client`` and ``$data`` arguments
  362. * and is responsible for encoding the data, authenticating, and sending
  363. * the data to the upstream Sentry server.
  364. *
  365. * @param Callable $value Function to be called
  366. * @return Raven_Client
  367. */
  368. public function setTransport($value)
  369. {
  370. $this->transport = $value;
  371. return $this;
  372. }
  373. /**
  374. * @return string[]|Raven_Processor[]
  375. */
  376. public static function getDefaultProcessors()
  377. {
  378. return array(
  379. 'Raven_Processor_SanitizeDataProcessor',
  380. );
  381. }
  382. /**
  383. * Sets the Raven_Processor sub-classes to be used when data is processed before being
  384. * sent to Sentry.
  385. *
  386. * @param $options
  387. * @return Raven_Processor[]
  388. */
  389. public function setProcessorsFromOptions($options)
  390. {
  391. $processors = array();
  392. foreach (Raven_util::get($options, 'processors', static::getDefaultProcessors()) as $processor) {
  393. /**
  394. * @var Raven_Processor $new_processor
  395. * @var Raven_Processor|string $processor
  396. */
  397. $new_processor = new $processor($this);
  398. if (isset($options['processorOptions']) && is_array($options['processorOptions'])) {
  399. if (isset($options['processorOptions'][$processor])
  400. && method_exists($processor, 'setProcessorOptions')
  401. ) {
  402. $new_processor->setProcessorOptions($options['processorOptions'][$processor]);
  403. }
  404. }
  405. $processors[] = $new_processor;
  406. }
  407. return $processors;
  408. }
  409. /**
  410. * Parses a Raven-compatible DSN and returns an array of its values.
  411. *
  412. * @param string $dsn Raven compatible DSN
  413. * @return array parsed DSN
  414. *
  415. * @see http://raven.readthedocs.org/en/latest/config/#the-sentry-dsn
  416. */
  417. public static function parseDSN($dsn)
  418. {
  419. switch (strtolower($dsn)) {
  420. case '':
  421. case 'false':
  422. case '(false)':
  423. case 'empty':
  424. case '(empty)':
  425. case 'null':
  426. case '(null)':
  427. return array();
  428. }
  429. $url = parse_url($dsn);
  430. $scheme = (isset($url['scheme']) ? $url['scheme'] : '');
  431. if (!in_array($scheme, array('http', 'https'))) {
  432. throw new InvalidArgumentException(
  433. 'Unsupported Sentry DSN scheme: '.
  434. (!empty($scheme) ? $scheme : '<not set>')
  435. );
  436. }
  437. $netloc = (isset($url['host']) ? $url['host'] : null);
  438. $netloc .= (isset($url['port']) ? ':'.$url['port'] : null);
  439. $rawpath = (isset($url['path']) ? $url['path'] : null);
  440. if ($rawpath) {
  441. $pos = strrpos($rawpath, '/', 1);
  442. if ($pos !== false) {
  443. $path = substr($rawpath, 0, $pos);
  444. $project = substr($rawpath, $pos + 1);
  445. } else {
  446. $path = '';
  447. $project = substr($rawpath, 1);
  448. }
  449. } else {
  450. $project = null;
  451. $path = '';
  452. }
  453. $username = (isset($url['user']) ? $url['user'] : null);
  454. $password = (isset($url['pass']) ? $url['pass'] : null);
  455. if (empty($netloc) || empty($project) || empty($username) || empty($password)) {
  456. throw new InvalidArgumentException('Invalid Sentry DSN: ' . $dsn);
  457. }
  458. return array(
  459. 'server' => sprintf('%s://%s%s/api/%s/store/', $scheme, $netloc, $path, $project),
  460. 'project' => $project,
  461. 'public_key' => $username,
  462. 'secret_key' => $password,
  463. );
  464. }
  465. public function getLastError()
  466. {
  467. return $this->_lasterror;
  468. }
  469. /**
  470. * Given an identifier, returns a Sentry searchable string.
  471. *
  472. * @param mixed $ident
  473. * @return mixed
  474. * @codeCoverageIgnore
  475. */
  476. public function getIdent($ident)
  477. {
  478. // XXX: We don't calculate checksums yet, so we only have the ident.
  479. return $ident;
  480. }
  481. /**
  482. * @param string $message The message (primary description) for the event.
  483. * @param array $params params to use when formatting the message.
  484. * @param string $level Log level group
  485. * @param bool|array $stack
  486. * @param mixed $vars
  487. * @return string|null
  488. * @deprecated
  489. * @codeCoverageIgnore
  490. */
  491. public function message($message, $params = array(), $level = self::INFO,
  492. $stack = false, $vars = null)
  493. {
  494. return $this->captureMessage($message, $params, $level, $stack, $vars);
  495. }
  496. /**
  497. * @param Exception $exception
  498. * @return string|null
  499. * @deprecated
  500. * @codeCoverageIgnore
  501. */
  502. public function exception($exception)
  503. {
  504. return $this->captureException($exception);
  505. }
  506. /**
  507. * Log a message to sentry
  508. *
  509. * @param string $message The message (primary description) for the event.
  510. * @param array $params params to use when formatting the message.
  511. * @param array $data Additional attributes to pass with this event (see Sentry docs).
  512. * @param bool|array $stack
  513. * @param mixed $vars
  514. * @return string|null
  515. */
  516. public function captureMessage($message, $params = array(), $data = array(),
  517. $stack = false, $vars = null)
  518. {
  519. // Gracefully handle messages which contain formatting characters, but were not
  520. // intended to be used with formatting.
  521. if (!empty($params)) {
  522. $formatted_message = vsprintf($message, $params);
  523. } else {
  524. $formatted_message = $message;
  525. }
  526. if ($data === null) {
  527. $data = array();
  528. // support legacy method of passing in a level name as the third arg
  529. } elseif (!is_array($data)) {
  530. $data = array(
  531. 'level' => $data,
  532. );
  533. }
  534. $data['message'] = $formatted_message;
  535. $data['sentry.interfaces.Message'] = array(
  536. 'message' => $message,
  537. 'params' => $params,
  538. 'formatted' => $formatted_message,
  539. );
  540. return $this->capture($data, $stack, $vars);
  541. }
  542. /**
  543. * Log an exception to sentry
  544. *
  545. * @param \Throwable|\Exception $exception The Throwable/Exception object.
  546. * @param array $data Additional attributes to pass with this event (see Sentry docs).
  547. * @param mixed $logger
  548. * @param mixed $vars
  549. * @return string|null
  550. */
  551. public function captureException($exception, $data = null, $logger = null, $vars = null)
  552. {
  553. $has_chained_exceptions = PHP_VERSION_ID >= 50300;
  554. if (in_array(get_class($exception), $this->exclude)) {
  555. return null;
  556. }
  557. foreach ($this->excluded_exceptions as $exclude) {
  558. if ($exception instanceof $exclude) {
  559. return null;
  560. }
  561. }
  562. if ($data === null) {
  563. $data = array();
  564. }
  565. $exc = $exception;
  566. do {
  567. $exc_data = array(
  568. 'value' => $this->serializer->serialize($exc->getMessage()),
  569. 'type' => get_class($exc),
  570. );
  571. /**'exception'
  572. * Exception::getTrace doesn't store the point at where the exception
  573. * was thrown, so we have to stuff it in ourselves. Ugh.
  574. */
  575. $trace = $exc->getTrace();
  576. $frame_where_exception_thrown = array(
  577. 'file' => $exc->getFile(),
  578. 'line' => $exc->getLine(),
  579. );
  580. array_unshift($trace, $frame_where_exception_thrown);
  581. $exc_data['stacktrace'] = array(
  582. 'frames' => Raven_Stacktrace::get_stack_info(
  583. $trace, $this->trace, $vars, $this->message_limit, $this->prefixes,
  584. $this->app_path, $this->excluded_app_paths, $this->serializer, $this->reprSerializer
  585. ),
  586. );
  587. $exceptions[] = $exc_data;
  588. } while ($has_chained_exceptions && $exc = $exc->getPrevious());
  589. $data['exception'] = array(
  590. 'values' => array_reverse($exceptions),
  591. );
  592. if ($logger !== null) {
  593. $data['logger'] = $logger;
  594. }
  595. if (empty($data['level'])) {
  596. if (method_exists($exception, 'getSeverity')) {
  597. $data['level'] = $this->translateSeverity($exception->getSeverity());
  598. } else {
  599. $data['level'] = self::ERROR;
  600. }
  601. }
  602. return $this->capture($data, $trace, $vars);
  603. }
  604. /**
  605. * Capture the most recent error (obtained with ``error_get_last``).
  606. * @return string|null
  607. */
  608. public function captureLastError()
  609. {
  610. if (null === $error = error_get_last()) {
  611. return null;
  612. }
  613. $e = new ErrorException(
  614. @$error['message'], 0, @$error['type'],
  615. @$error['file'], @$error['line']
  616. );
  617. return $this->captureException($e);
  618. }
  619. /**
  620. * Log an query to sentry
  621. *
  622. * @param string|null $query
  623. * @param string $level
  624. * @param string $engine
  625. */
  626. public function captureQuery($query, $level = self::INFO, $engine = '')
  627. {
  628. $data = array(
  629. 'message' => $query,
  630. 'level' => $level,
  631. 'sentry.interfaces.Query' => array(
  632. 'query' => $query
  633. )
  634. );
  635. if ($engine !== '') {
  636. $data['sentry.interfaces.Query']['engine'] = $engine;
  637. }
  638. return $this->capture($data, false);
  639. }
  640. /**
  641. * Return the last captured event's ID or null if none available.
  642. */
  643. public function getLastEventID()
  644. {
  645. return $this->_last_event_id;
  646. }
  647. protected function registerDefaultBreadcrumbHandlers()
  648. {
  649. $handler = new Raven_Breadcrumbs_ErrorHandler($this);
  650. $handler->install();
  651. }
  652. protected function registerShutdownFunction()
  653. {
  654. if (!$this->_shutdown_function_has_been_set) {
  655. $this->_shutdown_function_has_been_set = true;
  656. register_shutdown_function(array($this, 'onShutdown'));
  657. }
  658. }
  659. /**
  660. * @return bool
  661. * @codeCoverageIgnore
  662. */
  663. protected static function is_http_request()
  664. {
  665. return isset($_SERVER['REQUEST_METHOD']) && PHP_SAPI !== 'cli';
  666. }
  667. protected function get_http_data()
  668. {
  669. $headers = array();
  670. foreach ($_SERVER as $key => $value) {
  671. if (0 === strpos($key, 'HTTP_')) {
  672. $header_key =
  673. str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))));
  674. $headers[$header_key] = $value;
  675. } elseif (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH')) && $value !== '') {
  676. $header_key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key))));
  677. $headers[$header_key] = $value;
  678. }
  679. }
  680. $result = array(
  681. 'method' => self::_server_variable('REQUEST_METHOD'),
  682. 'url' => $this->get_current_url(),
  683. 'query_string' => self::_server_variable('QUERY_STRING'),
  684. );
  685. // dont set this as an empty array as PHP will treat it as a numeric array
  686. // instead of a mapping which goes against the defined Sentry spec
  687. if (!empty($_POST)) {
  688. $result['data'] = $_POST;
  689. } elseif (isset($_SERVER['CONTENT_TYPE']) && stripos($_SERVER['CONTENT_TYPE'], 'application/json') === 0) {
  690. $raw_data = $this->getInputStream() ?: false;
  691. if ($raw_data !== false) {
  692. $result['data'] = (array) json_decode($raw_data, true) ?: null;
  693. }
  694. }
  695. if (!empty($_COOKIE)) {
  696. $result['cookies'] = $_COOKIE;
  697. }
  698. if (!empty($headers)) {
  699. $result['headers'] = $headers;
  700. }
  701. return array(
  702. 'request' => $result,
  703. );
  704. }
  705. protected function get_user_data()
  706. {
  707. $user = $this->context->user;
  708. if ($user === null) {
  709. if (!function_exists('session_id') || !session_id()) {
  710. return array();
  711. }
  712. $user = array(
  713. 'id' => session_id(),
  714. );
  715. if (!empty($_SERVER['REMOTE_ADDR'])) {
  716. $user['ip_address'] = $_SERVER['REMOTE_ADDR'];
  717. }
  718. if (!empty($_SESSION)) {
  719. $user['data'] = $_SESSION;
  720. }
  721. }
  722. return array(
  723. 'user' => $user,
  724. );
  725. }
  726. protected function get_extra_data()
  727. {
  728. return $this->extra_data;
  729. }
  730. public function get_default_data()
  731. {
  732. return array(
  733. 'server_name' => $this->name,
  734. 'project' => $this->project,
  735. 'site' => $this->site,
  736. 'logger' => $this->logger,
  737. 'tags' => $this->tags,
  738. 'platform' => 'php',
  739. 'sdk' => $this->sdk,
  740. 'culprit' => $this->transaction->peek(),
  741. );
  742. }
  743. public function capture($data, $stack = null, $vars = null)
  744. {
  745. if (!isset($data['timestamp'])) {
  746. $data['timestamp'] = gmdate('Y-m-d\TH:i:s\Z');
  747. }
  748. if (!isset($data['level'])) {
  749. $data['level'] = self::ERROR;
  750. }
  751. if (!isset($data['tags'])) {
  752. $data['tags'] = array();
  753. }
  754. if (!isset($data['extra'])) {
  755. $data['extra'] = array();
  756. }
  757. if (!isset($data['event_id'])) {
  758. $data['event_id'] = static::uuid4();
  759. }
  760. if (isset($data['message'])) {
  761. $data['message'] = substr($data['message'], 0, $this->message_limit);
  762. }
  763. $data = array_merge($this->get_default_data(), $data);
  764. if (static::is_http_request()) {
  765. $data = array_merge($this->get_http_data(), $data);
  766. }
  767. $data = array_merge($this->get_user_data(), $data);
  768. if ($this->release) {
  769. $data['release'] = $this->release;
  770. }
  771. if ($this->environment) {
  772. $data['environment'] = $this->environment;
  773. }
  774. $data['tags'] = array_merge(
  775. $this->tags,
  776. $this->context->tags,
  777. $data['tags']);
  778. $data['extra'] = array_merge(
  779. $this->get_extra_data(),
  780. $this->context->extra,
  781. $data['extra']);
  782. if (empty($data['extra'])) {
  783. unset($data['extra']);
  784. }
  785. if (empty($data['tags'])) {
  786. unset($data['tags']);
  787. }
  788. if (empty($data['user'])) {
  789. unset($data['user']);
  790. }
  791. if (empty($data['request'])) {
  792. unset($data['request']);
  793. }
  794. if (empty($data['site'])) {
  795. unset($data['site']);
  796. }
  797. $existing_runtime_context = isset($data['contexts']['runtime']) ? $data['contexts']['runtime'] : array();
  798. $runtime_context = array('version' => PHP_VERSION, 'name' => 'php');
  799. $data['contexts']['runtime'] = array_merge($runtime_context, $existing_runtime_context);
  800. if (!$this->breadcrumbs->is_empty()) {
  801. $data['breadcrumbs'] = $this->breadcrumbs->fetch();
  802. }
  803. if ((!$stack && $this->auto_log_stacks) || $stack === true) {
  804. $stack = debug_backtrace();
  805. // Drop last stack
  806. array_shift($stack);
  807. }
  808. if (! empty($stack) && ! isset($data['stacktrace']) && ! isset($data['exception'])) {
  809. $data['stacktrace'] = array(
  810. 'frames' => Raven_Stacktrace::get_stack_info(
  811. $stack, $this->trace, $vars, $this->message_limit, $this->prefixes,
  812. $this->app_path, $this->excluded_app_paths, $this->serializer, $this->reprSerializer
  813. ),
  814. );
  815. }
  816. $this->sanitize($data);
  817. $this->process($data);
  818. if (!$this->store_errors_for_bulk_send) {
  819. $this->send($data);
  820. } else {
  821. $this->_pending_events[] = $data;
  822. }
  823. $this->_last_event_id = $data['event_id'];
  824. return $data['event_id'];
  825. }
  826. public function sanitize(&$data)
  827. {
  828. // attempt to sanitize any user provided data
  829. if (!empty($data['request'])) {
  830. $data['request'] = $this->serializer->serialize($data['request'], 5);
  831. }
  832. if (!empty($data['user'])) {
  833. $data['user'] = $this->serializer->serialize($data['user'], 3);
  834. }
  835. if (!empty($data['extra'])) {
  836. $data['extra'] = $this->serializer->serialize($data['extra']);
  837. }
  838. if (!empty($data['tags'])) {
  839. foreach ($data['tags'] as $key => $value) {
  840. $data['tags'][$key] = @(string)$value;
  841. }
  842. }
  843. if (!empty($data['contexts'])) {
  844. $data['contexts'] = $this->serializer->serialize($data['contexts'], 5);
  845. }
  846. if (!empty($data['breadcrumbs'])) {
  847. $data['breadcrumbs'] = $this->serializer->serialize($data['breadcrumbs'], 5);
  848. }
  849. }
  850. /**
  851. * Process data through all defined Raven_Processor sub-classes
  852. *
  853. * @param array $data Associative array of data to log
  854. */
  855. public function process(&$data)
  856. {
  857. foreach ($this->processors as $processor) {
  858. $processor->process($data);
  859. }
  860. }
  861. public function sendUnsentErrors()
  862. {
  863. foreach ($this->_pending_events as $data) {
  864. $this->send($data);
  865. }
  866. $this->_pending_events = array();
  867. if ($this->store_errors_for_bulk_send) {
  868. //in case an error occurs after this is called, on shutdown, send any new errors.
  869. $this->store_errors_for_bulk_send = !defined('RAVEN_CLIENT_END_REACHED');
  870. }
  871. }
  872. /**
  873. * @param array $data
  874. * @return string|bool
  875. */
  876. public function encode(&$data)
  877. {
  878. $message = Raven_Compat::json_encode($data);
  879. if ($message === false) {
  880. if (function_exists('json_last_error_msg')) {
  881. $this->_lasterror = json_last_error_msg();
  882. } else {
  883. // @codeCoverageIgnoreStart
  884. $this->_lasterror = json_last_error();
  885. // @codeCoverageIgnoreEnd
  886. }
  887. return false;
  888. }
  889. if ($this->useCompression) {
  890. $message = gzcompress($message);
  891. }
  892. // PHP's builtin curl_* function are happy without this, but the exec method requires it
  893. $message = base64_encode($message);
  894. return $message;
  895. }
  896. /**
  897. * Wrapper to handle encoding and sending data to the Sentry API server.
  898. *
  899. * @param array $data Associative array of data to log
  900. */
  901. public function send(&$data)
  902. {
  903. if (is_callable($this->send_callback)
  904. && call_user_func_array($this->send_callback, array(&$data)) === false
  905. ) {
  906. // if send_callback returns false, end native send
  907. return;
  908. }
  909. if (!$this->server) {
  910. return;
  911. }
  912. if ($this->transport) {
  913. call_user_func($this->transport, $this, $data);
  914. return;
  915. }
  916. // should this event be sampled?
  917. if (rand(1, 100) / 100.0 > $this->sample_rate) {
  918. return;
  919. }
  920. $message = $this->encode($data);
  921. $headers = array(
  922. 'User-Agent' => static::getUserAgent(),
  923. 'X-Sentry-Auth' => $this->getAuthHeader(),
  924. 'Content-Type' => 'application/octet-stream'
  925. );
  926. $this->send_remote($this->server, $message, $headers);
  927. }
  928. /**
  929. * Send data to Sentry
  930. *
  931. * @param string $url Full URL to Sentry
  932. * @param array|string $data Associative array of data to log
  933. * @param array $headers Associative array of headers
  934. */
  935. protected function send_remote($url, $data, $headers = array())
  936. {
  937. $parts = parse_url($url);
  938. $parts['netloc'] = $parts['host'].(isset($parts['port']) ? ':'.$parts['port'] : null);
  939. $this->send_http($url, $data, $headers);
  940. }
  941. protected static function get_default_ca_cert()
  942. {
  943. return dirname(__FILE__) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cacert.pem';
  944. }
  945. /**
  946. * @return array
  947. * @see http://stackoverflow.com/questions/9062798/php-curl-timeout-is-not-working/9063006#9063006
  948. */
  949. protected function get_curl_options()
  950. {
  951. $options = array(
  952. CURLOPT_VERBOSE => false,
  953. CURLOPT_SSL_VERIFYHOST => 2,
  954. CURLOPT_SSL_VERIFYPEER => $this->verify_ssl,
  955. CURLOPT_CAINFO => $this->ca_cert,
  956. CURLOPT_USERAGENT => 'sentry-php/' . self::VERSION,
  957. );
  958. if ($this->http_proxy) {
  959. $options[CURLOPT_PROXY] = $this->http_proxy;
  960. }
  961. if ($this->curl_ssl_version) {
  962. $options[CURLOPT_SSLVERSION] = $this->curl_ssl_version;
  963. }
  964. if ($this->curl_ipv4) {
  965. $options[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
  966. }
  967. if (defined('CURLOPT_TIMEOUT_MS')) {
  968. // MS is available in curl >= 7.16.2
  969. $timeout = max(1, ceil(1000 * $this->timeout));
  970. // some versions of PHP 5.3 don't have this defined correctly
  971. if (!defined('CURLOPT_CONNECTTIMEOUT_MS')) {
  972. //see stackoverflow link in the phpdoc
  973. define('CURLOPT_CONNECTTIMEOUT_MS', 156);
  974. }
  975. $options[CURLOPT_CONNECTTIMEOUT_MS] = $timeout;
  976. $options[CURLOPT_TIMEOUT_MS] = $timeout;
  977. } else {
  978. // fall back to the lower-precision timeout.
  979. $timeout = max(1, ceil($this->timeout));
  980. $options[CURLOPT_CONNECTTIMEOUT] = $timeout;
  981. $options[CURLOPT_TIMEOUT] = $timeout;
  982. }
  983. return $options;
  984. }
  985. /**
  986. * Send the message over http to the sentry url given
  987. *
  988. * @param string $url URL of the Sentry instance to log to
  989. * @param array|string $data Associative array of data to log
  990. * @param array $headers Associative array of headers
  991. */
  992. protected function send_http($url, $data, $headers = array())
  993. {
  994. if ($this->curl_method == 'async') {
  995. $this->_curl_handler->enqueue($url, $data, $headers);
  996. } elseif ($this->curl_method == 'exec') {
  997. $this->send_http_asynchronous_curl_exec($url, $data, $headers);
  998. } else {
  999. $this->send_http_synchronous($url, $data, $headers);
  1000. }
  1001. }
  1002. protected function buildCurlCommand($url, $data, $headers)
  1003. {
  1004. // TODO(dcramer): support ca_cert
  1005. $cmd = $this->curl_path.' -X POST ';
  1006. foreach ($headers as $key => $value) {
  1007. $cmd .= '-H ' . escapeshellarg($key.': '.$value). ' ';
  1008. }
  1009. $cmd .= '-d ' . escapeshellarg($data) . ' ';
  1010. $cmd .= escapeshellarg($url) . ' ';
  1011. $cmd .= '-m 5 '; // 5 second timeout for the whole process (connect + send)
  1012. if (!$this->verify_ssl) {
  1013. $cmd .= '-k ';
  1014. }
  1015. $cmd .= '> /dev/null 2>&1 &'; // ensure exec returns immediately while curl runs in the background
  1016. return $cmd;
  1017. }
  1018. /**
  1019. * Send the cURL to Sentry asynchronously. No errors will be returned from cURL
  1020. *
  1021. * @param string $url URL of the Sentry instance to log to
  1022. * @param array|string $data Associative array of data to log
  1023. * @param array $headers Associative array of headers
  1024. * @return bool
  1025. */
  1026. protected function send_http_asynchronous_curl_exec($url, $data, $headers)
  1027. {
  1028. exec($this->buildCurlCommand($url, $data, $headers));
  1029. return true; // The exec method is just fire and forget, so just assume it always works
  1030. }
  1031. /**
  1032. * Send a blocking cURL to Sentry and check for errors from cURL
  1033. *
  1034. * @param string $url URL of the Sentry instance to log to
  1035. * @param array|string $data Associative array of data to log
  1036. * @param array $headers Associative array of headers
  1037. * @return bool
  1038. */
  1039. protected function send_http_synchronous($url, $data, $headers)
  1040. {
  1041. $new_headers = array();
  1042. foreach ($headers as $key => $value) {
  1043. array_push($new_headers, $key .': '. $value);
  1044. }
  1045. // XXX(dcramer): Prevent 100-continue response form server (Fixes GH-216)
  1046. $new_headers[] = 'Expect:';
  1047. if (is_null($this->_curl_instance)) {
  1048. $this->_curl_instance = curl_init($url);
  1049. }
  1050. curl_setopt($this->_curl_instance, CURLOPT_POST, 1);
  1051. curl_setopt($this->_curl_instance, CURLOPT_HTTPHEADER, $new_headers);
  1052. curl_setopt($this->_curl_instance, CURLOPT_POSTFIELDS, $data);
  1053. curl_setopt($this->_curl_instance, CURLOPT_RETURNTRANSFER, true);
  1054. $options = $this->get_curl_options();
  1055. if (isset($options[CURLOPT_CAINFO])) {
  1056. $ca_cert = $options[CURLOPT_CAINFO];
  1057. unset($options[CURLOPT_CAINFO]);
  1058. } else {
  1059. $ca_cert = null;
  1060. }
  1061. curl_setopt_array($this->_curl_instance, $options);
  1062. $buffer = curl_exec($this->_curl_instance);
  1063. $errno = curl_errno($this->_curl_instance);
  1064. // CURLE_SSL_CACERT || CURLE_SSL_CACERT_BADFILE
  1065. if ((($errno == 60) || ($errno == 77)) && !is_null($ca_cert)) {
  1066. curl_setopt($this->_curl_instance, CURLOPT_CAINFO, $ca_cert);
  1067. $buffer = curl_exec($this->_curl_instance);
  1068. }
  1069. if ($errno != 0) {
  1070. $this->_lasterror = curl_error($this->_curl_instance);
  1071. $this->_last_sentry_error = null;
  1072. return false;
  1073. }
  1074. $code = curl_getinfo($this->_curl_instance, CURLINFO_HTTP_CODE);
  1075. $success = ($code == 200);
  1076. if ($success) {
  1077. $this->_lasterror = null;
  1078. $this->_last_sentry_error = null;
  1079. } else {
  1080. // It'd be nice just to raise an exception here, but it's not very PHP-like
  1081. $this->_lasterror = curl_error($this->_curl_instance);
  1082. $this->_last_sentry_error = @json_decode($buffer);
  1083. }
  1084. return $success;
  1085. }
  1086. /**
  1087. * Generate a Sentry authorization header string
  1088. *
  1089. * @param string $timestamp Timestamp when the event occurred
  1090. * @param string $client HTTP client name (not Raven_Client object)
  1091. * @param string $api_key Sentry API key
  1092. * @param string $secret_key Sentry API key
  1093. * @return string
  1094. */
  1095. protected static function get_auth_header($timestamp, $client, $api_key, $secret_key)
  1096. {
  1097. $header = array(
  1098. sprintf('sentry_timestamp=%F', $timestamp),
  1099. "sentry_client={$client}",
  1100. sprintf('sentry_version=%s', self::PROTOCOL),
  1101. );
  1102. if ($api_key) {
  1103. $header[] = "sentry_key={$api_key}";
  1104. }
  1105. if ($secret_key) {
  1106. $header[] = "sentry_secret={$secret_key}";
  1107. }
  1108. return sprintf('Sentry %s', implode(', ', $header));
  1109. }
  1110. public function getAuthHeader()
  1111. {
  1112. $timestamp = microtime(true);
  1113. return $this->get_auth_header(
  1114. $timestamp, static::getUserAgent(), $this->public_key, $this->secret_key
  1115. );
  1116. }
  1117. /**
  1118. * Generate an uuid4 value
  1119. *
  1120. * @return string
  1121. */
  1122. protected static function uuid4()
  1123. {
  1124. $uuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
  1125. // 32 bits for "time_low"
  1126. mt_rand(0, 0xffff), mt_rand(0, 0xffff),
  1127. // 16 bits for "time_mid"
  1128. mt_rand(0, 0xffff),
  1129. // 16 bits for "time_hi_and_version",
  1130. // four most significant bits holds version number 4
  1131. mt_rand(0, 0x0fff) | 0x4000,
  1132. // 16 bits, 8 bits for "clk_seq_hi_res",
  1133. // 8 bits for "clk_seq_low",
  1134. // two most significant bits holds zero and one for variant DCE1.1
  1135. mt_rand(0, 0x3fff) | 0x8000,
  1136. // 48 bits for "node"
  1137. mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
  1138. );
  1139. return str_replace('-', '', $uuid);
  1140. }
  1141. /**
  1142. * Return the URL for the current request
  1143. *
  1144. * @return string|null
  1145. */
  1146. protected function get_current_url()
  1147. {
  1148. // When running from commandline the REQUEST_URI is missing.
  1149. if (!isset($_SERVER['REQUEST_URI'])) {
  1150. return null;
  1151. }
  1152. // HTTP_HOST is a client-supplied header that is optional in HTTP 1.0
  1153. $host = (!empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST']
  1154. : (!empty($_SERVER['LOCAL_ADDR']) ? $_SERVER['LOCAL_ADDR']
  1155. : (!empty($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '')));
  1156. $hasNonDefaultPort = !empty($_SERVER['SERVER_PORT']) && !in_array((int)$_SERVER['SERVER_PORT'], array(80, 443));
  1157. if ($hasNonDefaultPort && !preg_match('#:[0-9]*$#', $host)) {
  1158. $host .= ':' . $_SERVER['SERVER_PORT'];
  1159. }
  1160. $httpS = $this->isHttps() ? 's' : '';
  1161. return "http{$httpS}://{$host}{$_SERVER['REQUEST_URI']}";
  1162. }
  1163. /**
  1164. * Was the current request made over https?
  1165. *
  1166. * @return bool
  1167. */
  1168. protected function isHttps()
  1169. {
  1170. if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
  1171. return true;
  1172. }
  1173. if (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) {
  1174. return true;
  1175. }
  1176. if (!empty($this->trust_x_forwarded_proto) &&
  1177. !empty($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
  1178. $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
  1179. return true;
  1180. }
  1181. return false;
  1182. }
  1183. /**
  1184. * Get the value of a key from $_SERVER
  1185. *
  1186. * @param string $key Key whose value you wish to obtain
  1187. * @return string Key's value
  1188. */
  1189. private static function _server_variable($key)
  1190. {
  1191. if (isset($_SERVER[$key])) {
  1192. return $_SERVER[$key];
  1193. }
  1194. return '';
  1195. }
  1196. /**
  1197. * Translate a PHP Error constant into a Sentry log level group
  1198. *
  1199. * @param string $severity PHP E_$x error constant
  1200. * @return string Sentry log level group
  1201. */
  1202. public function translateSeverity($severity)
  1203. {
  1204. if (is_array($this->severity_map) && isset($this->severity_map[$severity])) {
  1205. return $this->severity_map[$severity];
  1206. }
  1207. switch ($severity) {
  1208. case E_ERROR: return Raven_Client::ERROR;
  1209. case E_WARNING: return Raven_Client::WARN;
  1210. case E_PARSE: return Raven_Client::ERROR;
  1211. case E_NOTICE: return Raven_Client::INFO;
  1212. case E_CORE_ERROR: return Raven_Client::ERROR;
  1213. case E_CORE_WARNING: return Raven_Client::WARN;
  1214. case E_COMPILE_ERROR: return Raven_Client::ERROR;
  1215. case E_COMPILE_WARNING: return Raven_Client::WARN;
  1216. case E_USER_ERROR: return Raven_Client::ERROR;
  1217. case E_USER_WARNING: return Raven_Client::WARN;
  1218. case E_USER_NOTICE: return Raven_Client::INFO;
  1219. case E_STRICT: return Raven_Client::INFO;
  1220. case E_RECOVERABLE_ERROR: return Raven_Client::ERROR;
  1221. }
  1222. if (PHP_VERSION_ID >= 50300) {
  1223. switch ($severity) {
  1224. case E_DEPRECATED: return Raven_Client::WARN;
  1225. case E_USER_DEPRECATED: return Raven_Client::WARN;
  1226. }
  1227. }
  1228. return Raven_Client::ERROR;
  1229. }
  1230. /**
  1231. * Provide a map of PHP Error constants to Sentry logging groups to use instead
  1232. * of the defaults in translateSeverity()
  1233. *
  1234. * @param array $map
  1235. */
  1236. public function registerSeverityMap($map)
  1237. {
  1238. $this->severity_map = $map;
  1239. }
  1240. /**
  1241. * Convenience function for setting a user's ID and Email
  1242. *
  1243. * @deprecated
  1244. * @param string $id User's ID
  1245. * @param string|null $email User's email
  1246. * @param array $data Additional user data
  1247. * @codeCoverageIgnore
  1248. */
  1249. public function set_user_data($id, $email = null, $data = array())
  1250. {
  1251. $user = array('id' => $id);
  1252. if (isset($email)) {
  1253. $user['email'] = $email;
  1254. }
  1255. $this->user_context(array_merge($user, $data));
  1256. }
  1257. public function onShutdown()
  1258. {
  1259. if (!defined('RAVEN_CLIENT_END_REACHED')) {
  1260. define('RAVEN_CLIENT_END_REACHED', true);
  1261. }
  1262. $this->sendUnsentErrors();
  1263. if ($this->curl_method == 'async') {
  1264. $this->_curl_handler->join();
  1265. }
  1266. }
  1267. /**
  1268. * Sets user context.
  1269. *
  1270. * @param array $data Associative array of user data
  1271. * @param bool $merge Merge existing context with new context
  1272. */
  1273. public function user_context($data, $merge = true)
  1274. {
  1275. if ($merge && $this->context->user !== null) {
  1276. // bail if data is null
  1277. if (!$data) {
  1278. return;
  1279. }
  1280. $this->context->user = array_merge($this->context->user, $data);
  1281. } else {
  1282. $this->context->user = $data;
  1283. }
  1284. }
  1285. /**
  1286. * Appends tags context.
  1287. *
  1288. * @param array $data Associative array of tags
  1289. */
  1290. public function tags_context($data)
  1291. {
  1292. $this->context->tags = array_merge($this->context->tags, $data);
  1293. }
  1294. /**
  1295. * Appends additional context.
  1296. *
  1297. * @param array $data Associative array of extra data
  1298. */
  1299. public function extra_context($data)
  1300. {
  1301. $this->context->extra = array_merge($this->context->extra, $data);
  1302. }
  1303. /**
  1304. * @param array $processors
  1305. */
  1306. public function setProcessors(array $processors)
  1307. {
  1308. $this->processors = $processors;
  1309. }
  1310. /**
  1311. * @return object|null
  1312. */
  1313. public function getLastSentryError()
  1314. {
  1315. return $this->_last_sentry_error;
  1316. }
  1317. /**
  1318. * @return bool
  1319. */
  1320. public function getShutdownFunctionHasBeenSet()
  1321. {
  1322. return $this->_shutdown_function_has_been_set;
  1323. }
  1324. public function close_curl_resource()
  1325. {
  1326. if (!is_null($this->_curl_instance)) {
  1327. curl_close($this->_curl_instance);
  1328. $this->_curl_instance = null;
  1329. }
  1330. }
  1331. /**
  1332. * @param Raven_Serializer $serializer
  1333. */
  1334. public function setSerializer(Raven_Serializer $serializer)
  1335. {
  1336. $this->serializer = $serializer;
  1337. }
  1338. /**
  1339. * @param Raven_ReprSerializer $reprSerializer
  1340. */
  1341. public function setReprSerializer(Raven_ReprSerializer $reprSerializer)
  1342. {
  1343. $this->reprSerializer = $reprSerializer;
  1344. }
  1345. private function triggerAutoload()
  1346. {
  1347. // manually trigger autoloading, as it cannot be done during error handling in some edge cases due to PHP (see #60149)
  1348. if (! class_exists('Raven_Stacktrace')) {
  1349. spl_autoload_call('Raven_Stacktrace');
  1350. }
  1351. if (function_exists('mb_detect_encoding')) {
  1352. mb_detect_encoding('string');
  1353. }
  1354. if (function_exists('mb_convert_encoding')) {
  1355. mb_convert_encoding('string', 'UTF8');
  1356. }
  1357. }
  1358. }