Feat: uncaught errors notification

This commit is contained in:
Alexander Pankratov 2024-07-14 20:42:33 +02:00
parent 3d6a97d4cf
commit d543b629a2
6 changed files with 100 additions and 7 deletions

View File

@ -15,7 +15,7 @@ TIMEZONE=UTC
# VALUE IN SECOND
# DECREASE TO REDUCE LATENCY, INCREASE TO REDUCE LOAD ON SERVER.
# SET to 0 to DISABLE
REQUESTS_BULK_INTERVAL=0.5
REQUESTS_BULK_INTERVAL=0.0
# List of allowed clients. Separate with comma.
# Leave blanc, to allow requests from all IP (THIS WILL MAKE API UNSECURE!)
@ -68,3 +68,10 @@ DB_SERIALIZER=serialize
DB_ENABLE_MIN_DATABASE=0
# Enable file metadata cache
DB_ENABLE_FILE_REFERENCE_DATABASE=0
ERROR_NOTIFICATION_BOT_TOKEN=
#User id or Chat id or username of the target channel to send error messages. Comma separated
#Example: 123456,@sometestchannel
ERROR_NOTIFICATION_PEERS=
# Program will continue to work after fatal error (not recommended)
RESUME_ON_ERROR=0

View File

@ -16,7 +16,7 @@ TIMEZONE=UTC
# VALUE IN SECOND
# DECREASE TO REDUCE LATENCY, INCREASE TO REDUCE LOAD ON SERVER.
# SET to 0 to DISABLE
REQUESTS_BULK_INTERVAL=0.5
REQUESTS_BULK_INTERVAL=0.0
# List of allowed clients. Separate with comma.
# Leave blanc, to allow requests from all IP (THIS WILL MAKE API UNSECURE!)
@ -66,3 +66,10 @@ DB_SERIALIZER=serialize
DB_ENABLE_MIN_DATABASE=0
# Enable file metadata cache
DB_ENABLE_FILE_REFERENCE_DATABASE=0
ERROR_NOTIFICATION_BOT_TOKEN=
#User id or Chat id or username of the target channel to send error messages. Comma separated
#Example: 123456,@sometestchannel
ERROR_NOTIFICATION_PEERS=
# Program will continue to work after fatal error (not recommended)
RESUME_ON_ERROR=0

View File

@ -27,6 +27,7 @@
"php": "^8.1",
"ext-json": "*",
"ext-mbstring": "*",
"ext-curl": "*",
"amphp/http-server": "^v3",
"amphp/http": "^v2",
"amphp/http-server-router": "^2",

View File

@ -9,6 +9,19 @@ $settings = [
'port' => (int)getenv('SERVER_PORT'),
'real_ip_header' => (string)(getenv('REAL_IP_HEADER') ?? ''),
],
'error' => [
'bot_token' => (string)getenv('ERROR_NOTIFICATION_BOT_TOKEN'),
'peers' => array_filter(
array_map(
static function(string $peer): string|int {
$peer = trim($peer);
return is_numeric($peer) ? (int)$peer : $peer;
},
explode(',', (string)getenv('ERROR_NOTIFICATION_PEERS'))
),
),
'resume_on_error' => ((bool)getenv('RESUME_ON_ERROR'))
],
'telegram' => [
'app_info' => [ // obtained in https://my.telegram.org
'api_id' => (int)getenv('TELEGRAM_API_ID'),

View File

@ -10,9 +10,11 @@ use danog\MadelineProto\SettingsAbstract;
use InvalidArgumentException;
use Psr\Log\LogLevel;
use ReflectionProperty;
use Revolt\EventLoop;
use RuntimeException;
use TelegramApiServer\EventObservers\EventHandler;
use TelegramApiServer\EventObservers\EventObserver;
use function Amp\async;
use function Amp\delay;
class Client
{
@ -32,6 +34,8 @@ class Client
{
warning(PHP_EOL . 'Starting MadelineProto...' . PHP_EOL);
$this->setFatalErrorHandler();
foreach ($sessionFiles as $file) {
$sessionName = Files::getSessionName($file);
$this->addSession($sessionName);
@ -160,7 +164,8 @@ class Client
return $wrapper;
}
private static function getSettingsFromArray(string $session, array $settings, SettingsAbstract $settingsObject = new Settings()): SettingsAbstract {
private static function getSettingsFromArray(string $session, array $settings, SettingsAbstract $settingsObject = new Settings()): SettingsAbstract
{
foreach ($settings as $key => $value) {
if (is_array($value) && $key !== 'proxies') {
if ($key === 'db' && isset($value['type'])) {
@ -198,5 +203,65 @@ class Client
return $settingsObject;
}
private function setFatalErrorHandler(): void
{
$token = Config::getInstance()->get('error.bot_token');
$peers = Config::getInstance()->get('error.peers');
$resume = Config::getInstance()->get('error.resume_on_error');
$currentHandler = EventLoop::getErrorHandler();
EventLoop::setErrorHandler(static fn(\Throwable $e) => self::errorHandler($e, $currentHandler, $token, $peers, $resume));
}
private static function errorHandler(\Throwable $e, ?callable $currentHandler, string $token, array $peers, bool $resume): void {
if ($currentHandler) {
$currentHandler($e);
}
if ($e->getPrevious()) {
self::errorHandler($e->getPrevious(), $currentHandler, $token, $peers, true);
}
if ($peers && $token) {
try {
$ch = curl_init("https://api.telegram.org/bot$token/sendMessage");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT,1);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
foreach ($peers as $peer) {
$exceptionArray = Logger::getExceptionAsArray($e);
unset($exceptionArray['previous_exception']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'chat_id' => $peer,
'text' => "```json\n" .
json_encode($exceptionArray, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT) .
"\n```"
,
'parse_mode' => 'MarkdownV2',
]));
$response = curl_exec($ch);
if (curl_getinfo($ch, CURLINFO_HTTP_CODE) !== 200) {
Logger::getInstance()->error('Error notification bot response', [
'response' => $response,
'error_code' => curl_errno($ch),
'error' => curl_error($ch),
]);
}
}
} catch (\Throwable $curlException) {
Logger::getInstance()->error($curlException);
}
}
if (!$resume) {
throw $e;
}
}
}

View File

@ -168,7 +168,7 @@ class Logger extends AbstractLogger
'line' => $exception->getLine(),
'code' => $exception->getCode(),
'backtrace' => array_slice($exception->getTrace(), 0, 3),
'previous exception' => $exception->getPrevious(),
'previous_exception' => $exception->getPrevious(),
];
}
}