From 940cc61c54b11e52dc4b197e3398140b953425df Mon Sep 17 00:00:00 2001 From: Benoit Galati Date: Mon, 1 Jul 2019 10:34:31 +0200 Subject: [PATCH] add: breadcrumbs to monolog handler --- composer.json | 3 ++ src/Monolog/Handler.php | 108 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 6a0a8c6b0a..7dcb24cec0 100644 --- a/composer.json +++ b/composer.json @@ -35,6 +35,9 @@ "phpunit/phpunit": "^7.0", "symfony/phpunit-bridge": "^4.1.6" }, + "suggest": { + "monolog/monolog": "If you want to use the monolog sentry handler." + }, "conflict": { "php-http/client-common": "1.8.0", "raven/raven": "*" diff --git a/src/Monolog/Handler.php b/src/Monolog/Handler.php index d1a7ea1b31..ca5bacd53a 100644 --- a/src/Monolog/Handler.php +++ b/src/Monolog/Handler.php @@ -6,6 +6,7 @@ use Monolog\Handler\AbstractProcessingHandler; use Monolog\Logger; +use Sentry\Breadcrumb; use Sentry\Severity; use Sentry\State\HubInterface; use Sentry\State\Scope; @@ -23,6 +24,11 @@ final class Handler extends AbstractProcessingHandler */ private $hub; + /** + * @var array + */ + protected $breadcrumbsBuffer = []; + /** * Constructor. * @@ -37,6 +43,50 @@ public function __construct(HubInterface $hub, $level = Logger::DEBUG, bool $bub $this->hub = $hub; parent::__construct($level, $bubble); + + $this->hub = $hub; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records): void + { + if (!$records) { + return; + } + + // filter records + $records = array_filter( + $records, + function ($record) { + return $record['level'] >= $this->level; + } + ); + + // the record with the highest severity is the "main" one + $main = array_reduce( + $records, + static function ($highest, $record) { + if ($record['level'] > $highest['level']) { + return $record; + } + + return $highest; + } + ); + + // the other ones are added as a context items + foreach ($records as $record) { + $record = $this->processRecord($record); + $record['formatted'] = $this->getFormatter()->format($record); + + $this->breadcrumbsBuffer[] = $record; + } + + $this->handle($main); + + $this->breadcrumbsBuffer = []; } /** @@ -56,19 +106,29 @@ protected function write(array $record): void $this->hub->withScope(function (Scope $scope) use ($record, $payload): void { $scope->setExtra('monolog.channel', $record['channel']); $scope->setExtra('monolog.level', $record['level_name']); + $scope->setExtra('monolog.formatted', $record['formatted']); if (isset($record['context']['extra']) && \is_array($record['context']['extra'])) { foreach ($record['context']['extra'] as $key => $value) { - $scope->setExtra((string) $key, $value); + $scope->setExtra($key, $value); } } if (isset($record['context']['tags']) && \is_array($record['context']['tags'])) { foreach ($record['context']['tags'] as $key => $value) { - $scope->setTag($key, $value); + $scope->setTag((string) $key, $value ?? 'N/A'); } } + foreach ($this->breadcrumbsBuffer as $breadcrumbRecord) { + $scope->addBreadcrumb(new Breadcrumb( + $this->getBreadcrumbLevelFromLevel($breadcrumbRecord['level']), + $this->getBreadcrumbTypeFromLevel($breadcrumbRecord['level']), + $breadcrumbRecord['channel'] ?? 'N/A', + $breadcrumbRecord['formatted'] + )); + } + $this->hub->captureEvent($payload); }); } @@ -100,4 +160,48 @@ private function getSeverityFromLevel(int $level): Severity return Severity::info(); } } + + /** + * Translates the Monolog level into the Sentry breadcrumb level. + * + * @param int $level The Monolog log level + * + * @return string + */ + private function getBreadcrumbLevelFromLevel(int $level): string + { + switch ($level) { + case Logger::DEBUG: + return Breadcrumb::LEVEL_DEBUG; + case Logger::INFO: + case Logger::NOTICE: + return Breadcrumb::LEVEL_INFO; + case Logger::WARNING: + return Breadcrumb::LEVEL_WARNING; + case Logger::ERROR: + return Breadcrumb::LEVEL_ERROR; + case Logger::CRITICAL: + case Logger::ALERT: + case Logger::EMERGENCY: + return Breadcrumb::LEVEL_CRITICAL; + default: + return Breadcrumb::LEVEL_CRITICAL; + } + } + + /** + * Translates the Monolog level into the Sentry breadcrumb type. + * + * @param int $level The Monolog log level + * + * @return string + */ + private function getBreadcrumbTypeFromLevel(int $level): string + { + if ($level >= Logger::ERROR) { + return Breadcrumb::TYPE_ERROR; + } + + return Breadcrumb::TYPE_DEFAULT; + } }