Skip to content

Commit 5940ca8

Browse files
committed
add: breadcrumbs to monolog handler
1 parent e85c748 commit 5940ca8

File tree

2 files changed

+108
-1
lines changed

2 files changed

+108
-1
lines changed

composer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
"phpunit/phpunit": "^7.0",
3636
"symfony/phpunit-bridge": "^4.1.6"
3737
},
38+
"suggest": {
39+
"monolog/monolog": "If you want to use the monolog sentry handler."
40+
},
3841
"conflict": {
3942
"php-http/client-common": "1.8.0",
4043
"raven/raven": "*"

src/Monolog/Handler.php

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Monolog\Handler\AbstractProcessingHandler;
88
use Monolog\Logger;
9+
use Sentry\Breadcrumb;
910
use Sentry\Severity;
1011
use Sentry\State\HubInterface;
1112
use Sentry\State\Scope;
@@ -23,6 +24,11 @@ final class Handler extends AbstractProcessingHandler
2324
*/
2425
private $hub;
2526

27+
/**
28+
* @var array
29+
*/
30+
protected $breadcrumbsBuffer = [];
31+
2632
/**
2733
* Constructor.
2834
*
@@ -37,6 +43,50 @@ public function __construct(HubInterface $hub, $level = Logger::DEBUG, bool $bub
3743
$this->hub = $hub;
3844

3945
parent::__construct($level, $bubble);
46+
47+
$this->hub = $hub;
48+
}
49+
50+
/**
51+
* {@inheritdoc}
52+
*/
53+
public function handleBatch(array $records): void
54+
{
55+
if (!$records) {
56+
return;
57+
}
58+
59+
// filter records
60+
$records = array_filter(
61+
$records,
62+
function ($record) {
63+
return $record['level'] >= $this->level;
64+
}
65+
);
66+
67+
// the record with the highest severity is the "main" one
68+
$main = array_reduce(
69+
$records,
70+
static function ($highest, $record) {
71+
if ($record['level'] > $highest['level']) {
72+
return $record;
73+
}
74+
75+
return $highest;
76+
}
77+
);
78+
79+
// the other ones are added as a context items
80+
foreach ($records as $record) {
81+
$record = $this->processRecord($record);
82+
$record['formatted'] = $this->getFormatter()->format($record);
83+
84+
$this->breadcrumbsBuffer[] = $record;
85+
}
86+
87+
$this->handle($main);
88+
89+
$this->breadcrumbsBuffer = [];
4090
}
4191

4292
/**
@@ -56,6 +106,7 @@ protected function write(array $record): void
56106
$this->hub->withScope(function (Scope $scope) use ($record, $payload): void {
57107
$scope->setExtra('monolog.channel', $record['channel']);
58108
$scope->setExtra('monolog.level', $record['level_name']);
109+
$scope->setExtra('monolog.formatted', $record['formatted']);
59110

60111
if (isset($record['context']['extra']) && \is_array($record['context']['extra'])) {
61112
foreach ($record['context']['extra'] as $key => $value) {
@@ -65,10 +116,19 @@ protected function write(array $record): void
65116

66117
if (isset($record['context']['tags']) && \is_array($record['context']['tags'])) {
67118
foreach ($record['context']['tags'] as $key => $value) {
68-
$scope->setTag($key, $value);
119+
$scope->setTag((string) $key, $value ?? 'N/A');
69120
}
70121
}
71122

123+
foreach ($this->breadcrumbsBuffer as $breadcrumbRecord) {
124+
$scope->addBreadcrumb(new Breadcrumb(
125+
$this->getBreadcrumbLevelFromLevel($breadcrumbRecord['level']),
126+
$this->getBreadcrumbTypeFromLevel($breadcrumbRecord['level']),
127+
$breadcrumbRecord['channel'] ?? 'N/A',
128+
$breadcrumbRecord['formatted']
129+
));
130+
}
131+
72132
$this->hub->captureEvent($payload);
73133
});
74134
}
@@ -100,4 +160,48 @@ private function getSeverityFromLevel(int $level): Severity
100160
return Severity::info();
101161
}
102162
}
163+
164+
/**
165+
* Translates the Monolog level into the Sentry breadcrumb level.
166+
*
167+
* @param int $level The Monolog log level
168+
*
169+
* @return string
170+
*/
171+
private function getBreadcrumbLevelFromLevel(int $level): string
172+
{
173+
switch ($level) {
174+
case Logger::DEBUG:
175+
return Breadcrumb::LEVEL_DEBUG;
176+
case Logger::INFO:
177+
case Logger::NOTICE:
178+
return Breadcrumb::LEVEL_INFO;
179+
case Logger::WARNING:
180+
return Breadcrumb::LEVEL_WARNING;
181+
case Logger::ERROR:
182+
return Breadcrumb::LEVEL_ERROR;
183+
case Logger::CRITICAL:
184+
case Logger::ALERT:
185+
case Logger::EMERGENCY:
186+
return Breadcrumb::LEVEL_CRITICAL;
187+
default:
188+
return Breadcrumb::LEVEL_CRITICAL;
189+
}
190+
}
191+
192+
/**
193+
* Translates the Monolog level into the Sentry breadcrumb type.
194+
*
195+
* @param int $level The Monolog log level
196+
*
197+
* @return string
198+
*/
199+
private function getBreadcrumbTypeFromLevel(int $level): string
200+
{
201+
if ($level >= Logger::ERROR) {
202+
return Breadcrumb::TYPE_ERROR;
203+
}
204+
205+
return Breadcrumb::TYPE_DEFAULT;
206+
}
103207
}

0 commit comments

Comments
 (0)