Skip to content

Commit 7098cf6

Browse files
authored
feat: add handler to convert logs to sentry issues (#2075)
1 parent 89fdff0 commit 7098cf6

4 files changed

Lines changed: 417 additions & 3 deletions

File tree

src/Monolog/Handler.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
* hub instance.
1818
*
1919
* @deprecated since version 4.24. To be removed in version 5.0. Use {@see LogsHandler}
20-
* with the `enable_logs` SDK option instead for logging. {@see ExceptionToSentryIssueHandler}
21-
* to send monolog exceptions to Sentry.
20+
* with the `enable_logs` SDK option for Sentry logs, {@see ExceptionToSentryIssueHandler}
21+
* to send Monolog exceptions to Sentry issues, and {@see LogToSentryIssueHandler}
22+
* to send Monolog log messages to Sentry issues.
2223
*
2324
* @author Stefano Arlandini <sarlandini@alice.it>
2425
*/
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sentry\Monolog;
6+
7+
use Monolog\Handler\AbstractProcessingHandler;
8+
use Monolog\Level;
9+
use Monolog\Logger;
10+
use Monolog\LogRecord;
11+
use Psr\Log\LogLevel;
12+
use Sentry\Event;
13+
use Sentry\EventHint;
14+
use Sentry\State\HubInterface;
15+
use Sentry\State\Scope;
16+
17+
/**
18+
* This Monolog handler captures log messages as Sentry issues.
19+
*/
20+
class LogToSentryIssueHandler extends AbstractProcessingHandler
21+
{
22+
use CompatibilityProcessingHandlerTrait;
23+
24+
private const CONTEXT_EXCEPTION_KEY = 'exception';
25+
26+
/**
27+
* @var HubInterface
28+
*/
29+
private $hub;
30+
31+
/**
32+
* @var bool
33+
*/
34+
private $fillExtraContext;
35+
36+
/**
37+
* @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $level
38+
*/
39+
public function __construct(HubInterface $hub, $level = Logger::DEBUG, bool $bubble = true, bool $fillExtraContext = false)
40+
{
41+
$this->hub = $hub;
42+
$this->fillExtraContext = $fillExtraContext;
43+
44+
parent::__construct($level, $bubble);
45+
}
46+
47+
/**
48+
* @param array<string, mixed>|LogRecord $record
49+
*/
50+
public function handle($record): bool
51+
{
52+
/** @phpstan-ignore-next-line */
53+
if (!$this->isHandling($record) || $this->hasThrowable($record)) {
54+
return false;
55+
}
56+
57+
/** @phpstan-ignore-next-line */
58+
return parent::handle($record);
59+
}
60+
61+
/**
62+
* @param array<string, mixed>|LogRecord $record
63+
*/
64+
protected function doWrite($record): void
65+
{
66+
$event = Event::createEvent();
67+
$event->setLevel(self::getSeverityFromLevel($record['level']));
68+
$event->setMessage($record['message']);
69+
$event->setLogger(\sprintf('monolog.%s', $record['channel']));
70+
71+
$hint = new EventHint();
72+
73+
$this->hub->withScope(function (Scope $scope) use ($record, $event, $hint): void {
74+
$scope->setExtra('monolog.channel', $record['channel']);
75+
$scope->setExtra('monolog.level', $record['level_name']);
76+
77+
if ($this->fillExtraContext) {
78+
$monologContextData = $this->getArrayFieldFromRecord($record, 'context');
79+
80+
if ($monologContextData !== []) {
81+
$scope->setExtra('monolog.context', $monologContextData);
82+
}
83+
84+
$monologExtraData = $this->getArrayFieldFromRecord($record, 'extra');
85+
86+
if ($monologExtraData !== []) {
87+
$scope->setExtra('monolog.extra', $monologExtraData);
88+
}
89+
}
90+
91+
$this->hub->captureEvent($event, $hint);
92+
});
93+
}
94+
95+
/**
96+
* @param array<string, mixed>|LogRecord $record
97+
*/
98+
private function hasThrowable($record): bool
99+
{
100+
$exception = $this->getArrayFieldFromRecord($record, 'context')[self::CONTEXT_EXCEPTION_KEY] ?? null;
101+
102+
return $exception instanceof \Throwable;
103+
}
104+
105+
/**
106+
* @param array<string, mixed>|LogRecord $record
107+
*
108+
* @return array<string, mixed>
109+
*/
110+
private function getArrayFieldFromRecord($record, string $field): array
111+
{
112+
if (isset($record[$field]) && \is_array($record[$field])) {
113+
return $record[$field];
114+
}
115+
116+
return [];
117+
}
118+
}

src/Monolog/LogsHandler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public function handle($record): bool
6363
if (!$this->isHandling($record)) {
6464
return false;
6565
}
66-
// Do not collect logs for exceptions, they should be handled seperately by the `Handler` or `captureException`
66+
// Do not collect logs for exceptions, they should be handled separately by `ExceptionToSentryIssueHandler` or `captureException`
6767
if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) {
6868
return false;
6969
}

0 commit comments

Comments
 (0)