Client.php 45 KB

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