1+ <?php
2+
3+ namespace Proklung \Error \Notifier ;
4+
5+ use Bitrix \Main \Application ;
6+ use Bitrix \Main \ArgumentException ;
7+ use Bitrix \Main \Diag \ExceptionHandlerLog ;
8+ use Bitrix \Main \ModuleManager ;
9+ use Bitrix \Main \ObjectPropertyException ;
10+ use Bitrix \Main \SystemException ;
11+ use Bitrix \Main \Type \DateTime ;
12+ use Exception ;
13+ use Bitrix \Main \Config \Option ;
14+ use Proklung \Notifier \Bitrix \Notifier \BitrixNotification ;
15+ use RuntimeException ;
16+ use Symfony \Component \Notifier \Notification \Notification ;
17+ use Symfony \Component \Notifier \NotifierInterface ;
18+ use Symfony \Component \Notifier \Recipient \Recipient ;
19+
20+ /**
21+ * Class ErrorHandler
22+ * @package Proklung\Error\Notifier
23+ *
24+ * @since 01.08.2021
25+ */
26+ class ErrorHandler extends ExceptionHandlerLog
27+ {
28+ private const OPTION_TYPES = 'types ' ;
29+
30+ /**
31+ * @var boolean[] $logTypeFlags
32+ */
33+ private $ logTypeFlags ;
34+
35+ /**
36+ * @var array $options
37+ */
38+ private $ options = [];
39+
40+ /**
41+ * ErrorHandler constructor.
42+ */
43+ public function __construct ()
44+ {
45+ /**
46+ * По-умолчанию логируется всё, кроме LOW_PRIORITY_ERROR.
47+ * Этот тип ошибки засоряет логи и появляется не только часто,
48+ * но и происходит от ошибок в коде ядра Битрикс.
49+ */
50+ $ this ->logTypeFlags = [
51+ self ::UNCAUGHT_EXCEPTION => true ,
52+ self ::CAUGHT_EXCEPTION => true ,
53+ self ::ASSERTION => true ,
54+ self ::FATAL => true ,
55+ ];
56+ }
57+
58+ /**
59+ * @inheritdoc
60+ */
61+ public function initialize (array $ options )
62+ {
63+ $ this ->initTypes ($ options );
64+
65+ $ this ->options = $ options ;
66+ }
67+
68+ /**
69+ * @inheritdoc
70+ * @throws ArgumentException | SystemException
71+ * @throws Exception
72+ */
73+ public function write ($ exception , $ logType )
74+ {
75+ if ((!array_key_exists ($ logType , $ this ->logTypeFlags )
76+ || true !== $ this ->logTypeFlags [$ logType ])
77+ ||
78+ // Только если этот proklung.notifier установлен
79+ !ModuleManager::isModuleInstalled ('proklung.notifier ' )
80+ ||
81+ !in_array ($ this ->options ['env ' ], $ this ->options ['allowed_env ' ], true )
82+ ) {
83+ return ;
84+ }
85+
86+ if ($ exception instanceof Exception && !$ this ->has ($ exception )) {
87+ $ this ->send ($ exception );
88+
89+ ErrorLogTable::add (
90+ [
91+ 'DATE_CREATE ' => new DateTime (),
92+ 'MD5 ' => md5 (serialize ($ exception )),
93+ 'EXCEPTION ' => serialize ($ exception ),
94+ ]
95+ );
96+ }
97+ }
98+
99+ /**
100+ * @param Exception $exception
101+ *
102+ * @return void
103+ * @throws Exception
104+ */
105+ private function send (Exception $ exception ) : void
106+ {
107+ /** @var NotifierInterface $notifier */
108+ $ notifier = \Proklung \Notifier \DI \Services::get ('notifier ' );
109+ $ importancy = $ this ->options ['importancy ' ] ?? Notification::IMPORTANCE_URGENT ;
110+
111+ $ envelope = \Proklung \Notifier \DI \Services::getParameter ('envelope ' );
112+ $ emails = $ envelope ['recipients ' ] ?? [];
113+ if (!$ emails ) {
114+ throw new RuntimeException ('Email of recipitients not setting. ' );
115+ }
116+
117+ $ email = $ emails [0 ];
118+
119+ if ($ this ->options ['recipient ' ]) {
120+ $ email = (string )$ this ->options ['recipient ' ];
121+ }
122+
123+ $ request = Application::getInstance ()->getContext ()->getRequest ();
124+ $ uriString = $ request ->getRequestUri ();
125+
126+ $ title = $ request ->getServer ()->getServerName ();
127+ if (!$ title ) {
128+ $ title = Option::get ('main ' , 'server_name ' , '' );
129+ }
130+
131+ $ body = 'Url: ' . $ uriString . ' ' ;
132+ $ body = $ body . get_class ($ exception ) .
133+ ': ' . $ exception ->getMessage () .
134+ ' in ' . $ exception ->getFile () .
135+ ' at line ' . $ exception ->getLine ();
136+
137+ $ notification = (new BitrixNotification ($ title ))
138+ ->content ($ body )
139+ ->importance ($ importancy );
140+
141+ $ notifier ->send ($ notification , new Recipient ($ email ));
142+ }
143+
144+ /**
145+ * Есть запись о такой ошибке в таблице?
146+ *
147+ * @param Exception $e Ошибка.
148+ *
149+ * @return boolean
150+ *
151+ * @throws ArgumentException | SystemException | ObjectPropertyException ORM ошибки.
152+ */
153+ private function has (Exception $ e ) : bool
154+ {
155+ $ hash = md5 (serialize ($ e ));
156+
157+ $ query = ErrorLogTable::getList ([
158+ 'filter ' => [
159+ '=MD5 ' => $ hash
160+ ]
161+ ]);
162+
163+ return count ($ query ->fetchAll ()) > 0 ;
164+ }
165+
166+ /**
167+ * @param array $options Опции.
168+ *
169+ * @return void
170+ */
171+ private function initTypes (array $ options ) : void
172+ {
173+ if (!array_key_exists (self ::OPTION_TYPES , $ options ) || !is_array ($ options [self ::OPTION_TYPES ])) {
174+ return ;
175+ }
176+
177+ $ this ->logTypeFlags = [];
178+ foreach ($ options [self ::OPTION_TYPES ] as $ logType ) {
179+ if (is_int ($ logType )) {
180+ $ this ->logTypeFlags [$ logType ] = true ;
181+ }
182+ }
183+ }
184+ }
0 commit comments