Skip to content

Commit 3dddbd7

Browse files
committed
First commit
0 parents  commit 3dddbd7

File tree

13 files changed

+618
-0
lines changed

13 files changed

+618
-0
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.idea
2+
/vendor/
3+
vendor/
4+
composer.lock
5+
*.php.meta

composer.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "proklung/bitrix-error-notifier-module",
3+
"description": "Интеграция компонента Symfony Notifier в Битрикс",
4+
"type": "bitrix-d7-module",
5+
"keywords": [
6+
"bitrix",
7+
"bitrix-module",
8+
"bitrix-cms",
9+
"bitrix-d7",
10+
"symfony",
11+
"bitrix-symfony",
12+
"notifier"
13+
],
14+
"homepage": "https://github.com/ProklUng/bitrix.error.notifier.module",
15+
"license": "MIT",
16+
"authors": [
17+
{
18+
"name": "Gavrilov Fedy",
19+
"email": "fedor.gavrilov.75@mail.ru"
20+
}
21+
],
22+
23+
"support": {
24+
"issues": "https://github.com/ProklUng/bitrix.error.notifier.module/issues",
25+
"source": "https://github.com/ProklUng/bitrix.error.notifier.module"
26+
},
27+
"extra": {
28+
"installer-name": "proklung.error.notifier"
29+
},
30+
"require": {
31+
"php": ">=7.1 || ^8.0",
32+
"symfony/notifier": "^5",
33+
"proklung/bitrix.module.boilerplate": "^1.0",
34+
"composer/installers": "~1"
35+
},
36+
"require-dev": {
37+
"icanhazstring/composer-unused": "^0.7.5"
38+
}
39+
}

include.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
use Bitrix\Main\Loader;
4+
5+
Loader::registerAutoLoadClasses(
6+
'proklung.error.notifier',
7+
[
8+
'Proklung\Error\Notifier\ErrorLogTable' => 'lib/ErrorLogTable.php',
9+
]
10+
);

install/index.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
use Bitrix\Main\Application;
4+
use Bitrix\Main\ArgumentException;
5+
use Bitrix\Main\Entity\Base;
6+
use Bitrix\Main\Loader;
7+
use Bitrix\Main\Localization\Loc;
8+
use Bitrix\Main\ModuleManager;
9+
use Bitrix\Main\SystemException;
10+
use Proklung\Error\Notifier\ErrorLogTable;
11+
use ProklUng\Module\Boilerplate\Module;
12+
use ProklUng\Module\Boilerplate\ModuleUtilsTrait;
13+
14+
Loc::loadMessages(__FILE__);
15+
16+
class proklung_error_notifier extends CModule
17+
{
18+
use ModuleUtilsTrait;
19+
20+
/**
21+
* proklung_error_notifier constructor.
22+
*/
23+
public function __construct()
24+
{
25+
$arModuleVersion = [];
26+
27+
include __DIR__.'/version.php';
28+
29+
if (is_array($arModuleVersion)
30+
&&
31+
array_key_exists('VERSION', $arModuleVersion)) {
32+
$this->MODULE_VERSION = $arModuleVersion['VERSION'];
33+
$this->MODULE_VERSION_DATE = $arModuleVersion['VERSION_DATE'];
34+
}
35+
36+
$this->MODULE_FULL_NAME = 'error.notifier';
37+
$this->MODULE_VENDOR = 'proklung';
38+
$prefixLangCode = 'ERROR_NOTIFIER';
39+
40+
$this->MODULE_NAME = Loc::getMessage($prefixLangCode.'_MODULE_NAME');
41+
$this->MODULE_ID = $this->MODULE_VENDOR.'.'.$this->MODULE_FULL_NAME;
42+
$this->MODULE_TABLE_ENTITY = ErrorLogTable::class;
43+
44+
$this->MODULE_DESCRIPTION = Loc::getMessage($prefixLangCode.'_MODULE_DESCRIPTION');
45+
$this->MODULE_GROUP_RIGHTS = 'N';
46+
$this->PARTNER_NAME = Loc::getMessage($prefixLangCode.'_MODULE_PARTNER_NAME');
47+
$this->PARTNER_URI = Loc::getMessage($prefixLangCode.'MODULE_PARTNER_URI');
48+
49+
$this->moduleManager = new Module(
50+
[
51+
'MODULE_ID' => $this->MODULE_ID,
52+
'VENDOR_ID' => $this->MODULE_VENDOR,
53+
'MODULE_VERSION' => $this->MODULE_VERSION,
54+
'MODULE_VERSION_DATE' => $this->MODULE_VERSION_DATE,
55+
'ADMIN_FORM_ID' => $this->MODULE_VENDOR.'_settings_form',
56+
]
57+
);
58+
59+
$this->moduleManager->addModuleInstance($this);
60+
$this->options();
61+
}
62+
63+
/**
64+
* @inheritDoc
65+
*/
66+
public function installDB()
67+
{
68+
if (Loader::includeModule($this->MODULE_ID)) {
69+
if (!Application::getConnection()->isTableExists(
70+
Base::getInstance(ErrorLogTable::class)->getDBTableName()
71+
)) {
72+
Base::getInstance(ErrorLogTable::class)->createDBTable();
73+
}
74+
}
75+
76+
if (!Application::getConnection()->isTableExists(
77+
Base::getInstance(ErrorLogTable::class)->getDBTableName()
78+
)) {
79+
throw new RuntimeException(
80+
'Table for module ' . $this->MODULE_ID . ' not created'
81+
);
82+
}
83+
}
84+
85+
/**
86+
* @inheritDoc
87+
*/
88+
public function uninstallDB()
89+
{
90+
$connection = Application::getInstance()->getConnection();
91+
try {
92+
$connection->dropTable(
93+
Base::getInstance(ErrorLogTable::class)->getDBTableName()
94+
);
95+
} catch (Exception $e) {
96+
// Ошибки типа таблица не найдена - глушатся.
97+
}
98+
}
99+
}

install/version.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
3+
$arModuleVersion = [
4+
'VERSION' => '1.0.0',
5+
'VERSION_DATE' => '2021-08-01'
6+
];

lang/ru/install/index.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
3+
$MESS['ERROR_NOTIFIER_MODULE_NAME'] = 'Сообщение о фатальных ошибках на сайте через Symfony Notifier';
4+
$MESS['ERROR_NOTIFIER_MODULE_DESCRIPTION'] = 'Сообщение о фатальных ошибках на сайте через Symfony Notifier';
5+
$MESS['ERROR_NOTIFIER_MODULE_PARTNER_NAME'] = 'Gavrilov Fedor';
6+
$MESS['ERROR_NOTIFIER_MODULE_PARTNER_URI'] = 'https://github.com/ProklUng';

lang/ru/options.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php
2+
defined('B_PROLOG_INCLUDED') and (B_PROLOG_INCLUDED === true) or die();
3+

lib/ClearTableAgent.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace Proklung\Error\Notifier;
4+
5+
use Bitrix\Main\Application;
6+
7+
/**
8+
* Class ClearTableAgent
9+
* @package Proklung\Error\Notifier
10+
*
11+
* @since 01.08.2021
12+
*/
13+
class ClearTableAgent
14+
{
15+
/**
16+
* @return string
17+
*/
18+
public static function clear() : string
19+
{
20+
$connection = Application::getConnection();
21+
$connection->truncateTable('b_fatal_error_log');
22+
23+
return '\Proklung\Error\Notifier\ClearTableAgent::clear();';
24+
}
25+
}

lib/ErrorHandler.php

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
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

Comments
 (0)