RavenHandler.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. <?php
  2. /*
  3. * This file is part of the Monolog package.
  4. *
  5. * (c) Jordi Boggiano <j.boggiano@seld.be>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Monolog\Handler;
  11. use Monolog\Formatter\LineFormatter;
  12. use Monolog\Formatter\FormatterInterface;
  13. use Monolog\Logger;
  14. use Raven_Client;
  15. /**
  16. * Handler to send messages to a Sentry (https://github.com/getsentry/sentry) server
  17. * using sentry-php (https://github.com/getsentry/sentry-php)
  18. *
  19. * @author Marc Abramowitz <marc@marc-abramowitz.com>
  20. */
  21. class RavenHandler extends AbstractProcessingHandler
  22. {
  23. /**
  24. * Translates Monolog log levels to Raven log levels.
  25. */
  26. protected $logLevels = array(
  27. Logger::DEBUG => Raven_Client::DEBUG,
  28. Logger::INFO => Raven_Client::INFO,
  29. Logger::NOTICE => Raven_Client::INFO,
  30. Logger::WARNING => Raven_Client::WARNING,
  31. Logger::ERROR => Raven_Client::ERROR,
  32. Logger::CRITICAL => Raven_Client::FATAL,
  33. Logger::ALERT => Raven_Client::FATAL,
  34. Logger::EMERGENCY => Raven_Client::FATAL,
  35. );
  36. /**
  37. * @var string should represent the current version of the calling
  38. * software. Can be any string (git commit, version number)
  39. */
  40. protected $release;
  41. /**
  42. * @var Raven_Client the client object that sends the message to the server
  43. */
  44. protected $ravenClient;
  45. /**
  46. * @var LineFormatter The formatter to use for the logs generated via handleBatch()
  47. */
  48. protected $batchFormatter;
  49. /**
  50. * @param Raven_Client $ravenClient
  51. * @param int $level The minimum logging level at which this handler will be triggered
  52. * @param bool $bubble Whether the messages that are handled can bubble up the stack or not
  53. */
  54. public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true)
  55. {
  56. parent::__construct($level, $bubble);
  57. $this->ravenClient = $ravenClient;
  58. }
  59. /**
  60. * {@inheritdoc}
  61. */
  62. public function handleBatch(array $records)
  63. {
  64. $level = $this->level;
  65. // filter records based on their level
  66. $records = array_filter($records, function ($record) use ($level) {
  67. return $record['level'] >= $level;
  68. });
  69. if (!$records) {
  70. return;
  71. }
  72. // the record with the highest severity is the "main" one
  73. $record = array_reduce($records, function ($highest, $record) {
  74. if ($record['level'] > $highest['level']) {
  75. return $record;
  76. }
  77. return $highest;
  78. });
  79. // the other ones are added as a context item
  80. $logs = array();
  81. foreach ($records as $r) {
  82. $logs[] = $this->processRecord($r);
  83. }
  84. if ($logs) {
  85. $record['context']['logs'] = (string) $this->getBatchFormatter()->formatBatch($logs);
  86. }
  87. $this->handle($record);
  88. }
  89. /**
  90. * Sets the formatter for the logs generated by handleBatch().
  91. *
  92. * @param FormatterInterface $formatter
  93. */
  94. public function setBatchFormatter(FormatterInterface $formatter)
  95. {
  96. $this->batchFormatter = $formatter;
  97. }
  98. /**
  99. * Gets the formatter for the logs generated by handleBatch().
  100. *
  101. * @return FormatterInterface
  102. */
  103. public function getBatchFormatter()
  104. {
  105. if (!$this->batchFormatter) {
  106. $this->batchFormatter = $this->getDefaultBatchFormatter();
  107. }
  108. return $this->batchFormatter;
  109. }
  110. /**
  111. * {@inheritdoc}
  112. */
  113. protected function write(array $record)
  114. {
  115. $previousUserContext = false;
  116. $options = array();
  117. $options['level'] = $this->logLevels[$record['level']];
  118. $options['tags'] = array();
  119. if (!empty($record['extra']['tags'])) {
  120. $options['tags'] = array_merge($options['tags'], $record['extra']['tags']);
  121. unset($record['extra']['tags']);
  122. }
  123. if (!empty($record['context']['tags'])) {
  124. $options['tags'] = array_merge($options['tags'], $record['context']['tags']);
  125. unset($record['context']['tags']);
  126. }
  127. if (!empty($record['context']['fingerprint'])) {
  128. $options['fingerprint'] = $record['context']['fingerprint'];
  129. unset($record['context']['fingerprint']);
  130. }
  131. if (!empty($record['context']['logger'])) {
  132. $options['logger'] = $record['context']['logger'];
  133. unset($record['context']['logger']);
  134. } else {
  135. $options['logger'] = $record['channel'];
  136. }
  137. foreach ($this->getExtraParameters() as $key) {
  138. foreach (array('extra', 'context') as $source) {
  139. if (!empty($record[$source][$key])) {
  140. $options[$key] = $record[$source][$key];
  141. unset($record[$source][$key]);
  142. }
  143. }
  144. }
  145. if (!empty($record['context'])) {
  146. $options['extra']['context'] = $record['context'];
  147. if (!empty($record['context']['user'])) {
  148. $previousUserContext = $this->ravenClient->context->user;
  149. $this->ravenClient->user_context($record['context']['user']);
  150. unset($options['extra']['context']['user']);
  151. }
  152. }
  153. if (!empty($record['extra'])) {
  154. $options['extra']['extra'] = $record['extra'];
  155. }
  156. if (!empty($this->release) && !isset($options['release'])) {
  157. $options['release'] = $this->release;
  158. }
  159. if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) {
  160. $options['message'] = $record['formatted'];
  161. $this->ravenClient->captureException($record['context']['exception'], $options);
  162. } else {
  163. $this->ravenClient->captureMessage($record['formatted'], array(), $options);
  164. }
  165. if ($previousUserContext !== false) {
  166. $this->ravenClient->user_context($previousUserContext);
  167. }
  168. }
  169. /**
  170. * {@inheritDoc}
  171. */
  172. protected function getDefaultFormatter()
  173. {
  174. return new LineFormatter('[%channel%] %message%');
  175. }
  176. /**
  177. * Gets the default formatter for the logs generated by handleBatch().
  178. *
  179. * @return FormatterInterface
  180. */
  181. protected function getDefaultBatchFormatter()
  182. {
  183. return new LineFormatter();
  184. }
  185. /**
  186. * Gets extra parameters supported by Raven that can be found in "extra" and "context"
  187. *
  188. * @return array
  189. */
  190. protected function getExtraParameters()
  191. {
  192. return array('contexts', 'checksum', 'release', 'event_id');
  193. }
  194. /**
  195. * @param string $value
  196. * @return self
  197. */
  198. public function setRelease($value)
  199. {
  200. $this->release = $value;
  201. return $this;
  202. }
  203. }