]);
// Сохраняем лог в базе данных
if ($log->save()) {
+
+ // Проверка на наличие аналогичных записей перед отправкой в Telegram
+ if (!self::shouldSendToTelegram($log)) {
+ return; // Пропускаем отправку, если уже есть записи за текущую дату
+ }
+
// Формируем сообщение для отправки в Telegram с использованием MarkdownV2
- $errorMessage = sprintf(
- "*JavaScript Error Detected*\n\n" .
- "*URL:*\n```%s```\n\n" .
- "*Created At:*\n```%s```\n\n" .
- "*IP:*\n```%s```\n\n" .
- "*User Agent:*\n```%s```\n\n" .
- "*File:* `%s`\n" .
- "*Line:* `%s`, *Column:* `%s`\n" .
- "*Message:*\n```%s```\n\n" .
- "*Context:*\n```%s```",
- $this->controller->request->getReferrer() ?? '<no referrer>',
- date('Y-m-d H:i:s', $log->log_time),
- $log->ip,
- $log->user_agent,
- $log->file ?? '<no file>',
- $log->line ?? '<no line>',
- $log->col ?? '<no col>',
- $log->message,
- $log->context ?? '<no context>'
- );
+ $errorMessage = "⚠️*Ошибка JavaScript Обнаружена*⚠️\n\n";
+
+// Добавляем строки в сообщение только если параметры присутствуют
+ if ($url = $this->controller->request->getReferrer()) {
+ $errorMessage .= "*URL:*\n```" . self::escapeMarkdown($url) . "```\n\n";
+ }
+
+ if ($createdAt = date('Y-m-d H:i:s', $log->log_time)) {
+ $errorMessage .= "*Created At:*\n```" . self::escapeMarkdown($createdAt) . "```\n\n";
+ }
+
+ if ($ip = $log->ip) {
+ $errorMessage .= "*IP:*\n```" . self::escapeMarkdown($ip) . "```\n\n";
+ }
+
+ if ($userAgent = $log->user_agent) {
+ $errorMessage .= "*User Agent:*\n```" . self::escapeMarkdown($userAgent) . "```\n\n";
+ }
+
+ if ($file = $log->file) {
+ $errorMessage .= "*File:*\n```" . self::escapeMarkdown($file) . "```\n\n";
+ }
+
+ if ($line = $log->line) {
+ $errorMessage .= "*Line:*\n```" . self::escapeMarkdown($line) . "```\n\n";
+ }
+
+ if ($col = $log->col) {
+ $errorMessage .= "*Column:*\n```" . self::escapeMarkdown($col) . "```\n\n";
+ }
+
+ if ($message = $log->message) {
+ $errorMessage .= "*Message:*\n```" . self::escapeMarkdown($message) . "```\n\n";
+ }
+
+ if ($context = $log->context) {
+ $errorMessage .= "*Context:*\n```" . self::escapeMarkdown($context) . "```\n\n";
+ }
+
+
+
+ $disableNotification = false;
// Отправляем сообщение об ошибке в Telegram через TelegramService
- TelegramService::sendErrorToTelegramMessage($errorMessage);
+ TelegramService::sendErrorToTelegramMessage($errorMessage, $disableNotification);
+ }
+ }
+
+// Метод для проверки наличия аналогичных записей в ErrorLog за текущую дату
+ private static function shouldSendToTelegram($log)
+ {
+ $startOfDay = strtotime('today'); // Метка времени для начала текущего дня
+
+ // Запрос в таблицу ErrorLog для поиска аналогичных записей
+ $count = ErrorLog::find()
+ ->where([
+ 'ip' => $log->ip,
+ 'category' => $log->category,
+ 'level' => $log->level,
+ 'referrer' => $log->referrer,
+ 'user_agent' => $log->user_agent,
+ 'file' => $log->file,
+ 'line' => $log->line,
+ 'col' => $log->col,
+ 'message' => $log->message,
+ 'context' => $log->context,
+ ])
+ ->andWhere(['>=', 'log_time', $startOfDay]) // Фильтр по началу текущего дня
+ ->count();
+
+ return $count <= 1; // Отправляем только если нет более одной записи
+ }
+
+ private static function escapeMarkdown($text)
+ {
+ $specialChars = ['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!'];
+ foreach ($specialChars as $char) {
+ $text = str_replace($char, '\\' . $char, $text);
}
+ return $text;
}
}
'levels' => ['error', 'warning'],
],
[
- 'class' => 'yii_app\services\TelegramTarget',
+ 'class' => 'app\log\TelegramTarget',
'levels' => ['error', 'warning'],
// 'categories' => ['api.error', 'js.error', 'command.error'],
],
--- /dev/null
+<?php
+
+namespace app\controllers;
+
+use Yii;
+use yii\web\Controller;
+use yii_app\records\ApiErrorLog;
+use yii_app\records\ErrorLog;
+use yii_app\records\InfoLog;
+
+
+use yii_app\services\InfoLogService;
+use yii_app\services\LogService;
+
+class TestLogController extends Controller
+{
+ /**
+ * Действие для выбора типа ошибки и создания копии записи в соответствующей таблице.
+ */
+ public function actionGenerateLog($type)
+ {
+ switch ($type) {
+ case 'api_error':
+ $this->generateApiErrorLog();
+ break;
+
+ case 'error_log':
+ $this->generateErrorLog();
+ break;
+
+ case 'info_log':
+ $this->generateInfoLog();
+ break;
+
+ default:
+ throw new \yii\web\BadRequestHttpException("Invalid log type specified.");
+ }
+
+ return $this->redirect(['index']);
+ }
+
+ /**
+ * Создает копию записи в таблице ApiErrorLog с текущей датой.
+ */
+ protected function generateApiErrorLog()
+ {
+ $existingLog = ApiErrorLog::find()->orderBy(['created_at' => SORT_DESC])->one();
+ if ($existingLog) {
+ LogService::apiErrorLog($existingLog->payload); // Используем метод из LogService
+ }
+ }
+
+ /**
+ * Создает копию записи в таблице ErrorLog с текущей датой.
+ */
+ protected function generateErrorLog()
+ {
+ $existingLog = ErrorLog::find()->orderBy(['log_time' => SORT_DESC])->one();
+ if ($existingLog) {
+ $logController = new \app\controllers\LogController('log-controller', Yii::$app); // экземпляр контроллера
+ $action = new \yii_app\actions\log\CollectAction('collect', $logController);
+ $action->run(); // Запускаем действие, чтобы создать копию
+ }
+ }
+
+ /**
+ * Создает копию записи в таблице InfoLog с текущей датой.
+ */
+ protected function generateInfoLog()
+ {
+ $existingLog = InfoLog::find()->orderBy(['created_at' => SORT_DESC])->one();
+ if ($existingLog) {
+ InfoLogService::setInfoLog($existingLog->file, $existingLog->line, $existingLog->message, $existingLog->context);
+ }
+ }
+
+ /**
+ * Действие для отображения представления с кнопками.
+ */
+ public function actionIndex()
+ {
+ return $this->render('index');
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace app\log;
+
+use yii\base\BaseObject;
+use yii\base\InvalidConfigException;
+use yii\helpers\VarDumper;
+use yii\log\Logger;
+
+class Message extends BaseObject
+{
+ /**
+ * @var array raw message.
+ */
+ public $message;
+
+ /**
+ * @var bool whether the current request is a console request.
+ */
+ private $_isConsoleRequest;
+
+ public function __construct($message, $config = [])
+ {
+ $this->message = $message;
+ parent::__construct($config);
+ }
+
+ /**
+ * Returns the message category.
+ * @return string message category.
+ */
+ public function getCategory()
+ {
+ return $this->message[2];
+ }
+
+ /**
+ * Returns the command line.
+ * @return string|null command line, `null` if not available.
+ */
+ public function getCommandLine()
+ {
+ if (\Yii::$app === null || !$this->getIsConsoleRequest()) {
+ return null;
+ }
+
+ $params = [];
+ if (isset($_SERVER['argv'])) {
+ $params = $_SERVER['argv'];
+ }
+ return implode(' ', $params);
+ }
+
+ /**
+ * Returns whether the current request is a console request.
+ * @return bool whether the current request is a console request.
+ * @throws InvalidConfigException if unable to determine.
+ */
+ public function getIsConsoleRequest()
+ {
+ if ($this->_isConsoleRequest === null && \Yii::$app !== null) {
+ if (\Yii::$app->getRequest() instanceof \yii\console\Request) {
+ $this->_isConsoleRequest = true;
+ } elseif (\Yii::$app->getRequest() instanceof \yii\web\Request) {
+ $this->_isConsoleRequest = false;
+ }
+ }
+ if ($this->_isConsoleRequest === null) {
+ throw new InvalidConfigException('Unable to determine if the application is a console or web application.');
+ }
+
+ return $this->_isConsoleRequest;
+ }
+
+ /**
+ * Returns the message level as a string.
+ * @return string message level as a string.
+ */
+ public function getLevel()
+ {
+ return Logger::getLevelName($this->message[0][1]);
+ }
+
+ /**
+ * Returns a string to be prefixed to the message.
+ * @return string messsage prefix string.
+ */
+ public function getPrefix()
+ {
+ if ($this->target !== null) {
+ return $this->target->getMessagePrefix($this->message);
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Returns the session ID.
+ * @return string|null session ID, `null` if not available.
+ */
+ public function getSessionId()
+ {
+ if (
+ \Yii::$app !== null
+ && \Yii::$app->has('session', true)
+ && \Yii::$app->getSession() !== null
+ && \Yii::$app->getSession()->getIsActive()
+ ) {
+ return \Yii::$app->getSession()->getId();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the additional stack trace as a string.
+ * @return string|null stack trace, `null` if not available.
+ */
+ public function getStackTrace()
+ {
+ if (!isset($this->message[4]) || empty($this->message[4])) {
+ return null;
+ }
+
+ $traces = array_map(function ($trace) {
+ return "in {$trace['file']}:{$trace['line']}";
+ }, $this->message[4]);
+ return implode("\n", $traces);
+ }
+
+ /**
+ * Returns the message text.
+ * @return string message text.
+ */
+ public function getText()
+ {
+ $text = $this->message[0];
+ if (!is_string($text)) {
+ if ($text instanceof \Throwable) {
+ $text = (string) $text;
+ } else {
+ $text = VarDumper::export($text);
+ }
+ }
+ return $text;
+ }
+
+ /**
+ * Returns the message creation timestamp.
+ * @return float message creation timestamp.
+ */
+ public function getTimestamp()
+ {
+ return $this->message[3];
+ }
+
+ /**
+ * Returns the current absolute URL.
+ * @return null|string absolute URL, `null` if not available.
+ * @throws InvalidConfigException
+ */
+ public function getUrl()
+ {
+ if (\Yii::$app === null || $this->getIsConsoleRequest()) {
+ return null;
+ }
+
+ return \Yii::$app->getRequest()->getAbsoluteUrl();
+ }
+
+ /**
+ * Returns the user identity ID.
+ * @return int|string|null user identity ID, `null` if not available.
+ */
+ public function getUserId()
+ {
+ if (
+ \Yii::$app !== null
+ && \Yii::$app->has('user', true)
+ && \Yii::$app->getUser() !== null
+ ) {
+ $user = \Yii::$app->getUser()->getIdentity(false);
+ if ($user !== null) {
+ return $user->getId();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the user IP address.
+ * @return string|null user IP address, `null` if not available.
+ */
+ public function getUserIp()
+ {
+ if (\Yii::$app === null || $this->getIsConsoleRequest()) {
+ return null;
+ }
+
+ return \Yii::$app->getRequest()->getUserIP();
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace app\log;
+
+use Yii;
+use yii\base\InvalidConfigException;
+use yii\base\InvalidValueException;
+use yii\log\Logger;
+use app\log\Message;
+use yii_app\services\TelegramService;
+
+class TelegramTarget extends \yii\log\Target
+{
+ public bool $enable = true;
+
+ /**
+ * @var string bot token.
+ * @see https://core.telegram.org/bots/api#authorizing-your-bot
+ */
+
+
+ /**
+ * @var int|string unique identifier for the target chat or username of the target channel
+ * (in the format `{@}channelusername`).
+ */
+
+
+ private array $levelEmojis = [
+ Logger::LEVEL_ERROR => "☠ ",
+ Logger::LEVEL_WARNING => "⚠ ",
+ Logger::LEVEL_INFO => "ℹ ",
+ Logger::LEVEL_TRACE => "📝 ",
+ ];
+
+
+ private $errorTrackingFile = '@runtime/logs/error_tracking.log';
+
+// Метод для проверки и записи уникальной ошибки
+ private function shouldSendMessage($currentError)
+ {
+ $filePath = Yii::getAlias($this->errorTrackingFile);
+ $currentDate = date('Y-m-d');
+ $errorExists = false;
+ $errorLines = [];
+
+ // Загружаем существующие ошибки, если файл существует
+ if (file_exists($filePath)) {
+ $errorLines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
+ }
+
+ // Создаём хеш текущей ошибки после нормализации текста
+ $normalizedCurrentError = $this->normalizeText($currentError);
+ $currentErrorHash = md5($normalizedCurrentError);
+
+ foreach ($errorLines as &$line) {
+ [$date, $count, $hash, $errorText] = explode('|', $line, 4);
+
+ // Сравниваем хеши
+ if ($hash === $currentErrorHash) {
+ $errorExists = true;
+
+ // Если ошибка уже была за текущую дату
+ if ($date === $currentDate && $count >= 1) {
+ return false; // Не отправляем
+ }
+
+ // Увеличиваем счётчик и обновляем дату
+ $count++;
+ $line = implode('|', [$currentDate, $count, $hash, $errorText]);
+ break;
+ }
+ }
+
+ // Если ошибка новая, добавляем её в список
+ if (!$errorExists) {
+ $errorLines[] = implode('|', [$currentDate, 1, $currentErrorHash, $currentError]);
+ }
+
+ // Сохраняем обновлённый список ошибок
+ file_put_contents($filePath, implode(PHP_EOL, $errorLines) . PHP_EOL);
+
+ return true; // Ошибка не найдена или обновлена, можно отправлять
+ }
+
+// Метод для нормализации текста ошибки
+ private function normalizeText($text)
+ {
+ // Приводим текст к единому формату: нижний регистр, удаление лишних пробелов
+ $text = strtolower($text);
+ $text = preg_replace('/\s+/', ' ', $text);
+ return trim($text);
+ }
+
+ /**
+ * @throws InvalidConfigException
+ */
+ public function export()
+ {
+ if (!$this->enable) {
+ return;
+ }
+
+
+
+ $message = new Message($this->messages[0]);
+ $errorText = $message->getText();
+
+
+ // Проверка наличия подобных сообщений
+ if (!$this->shouldSendMessage($errorText)) {
+ return; // Пропускаем отправку, если ошибка уже зарегистрирована
+ }
+
+ // Формирование уровня и основного сообщения
+ $level = isset($this->levelEmojis[$message->message[1]]) ? $this->levelEmojis[$message->message[1]] . ' ' : '*' . ucfirst($message->getLevel()) . '* @ ';
+ if ($message->getIsConsoleRequest()) {
+ $level .= '```' . $message->getCommandLine() . '```';
+ } else {
+ $level .= '[' . $message->getUrl() . '](' . $message->getUrl() . ')';
+ }
+
+ // Текст сообщения
+ $text = [
+ $level,
+ " ",
+ "```" . mb_substr($message->getText(), 0, 3300) . "```",
+ " ",
+ "```" . $message->getStackTrace() . "```",
+ "🙂 " . $message->getUserIp(),
+ ];
+
+ $formattedMessage = implode("\n", $text);
+
+ // Настройка уведомления для сообщений уровня ERROR
+ $disableNotification = $message->message[1] !== Logger::LEVEL_ERROR;
+
+ // Отправка сообщения через TelegramService
+ try {
+ TelegramService::sendErrorToTelegramMessage($formattedMessage, $disableNotification);
+ } catch (\Exception $e) {
+ throw new InvalidValueException(
+ 'Unable to send logs to Telegram: ' . $e->getMessage(), $e->getCode()
+ );
+ }
+ }
+}
\ No newline at end of file
namespace yii_app\services;
+use yii\db\Expression;
use yii_app\records\InfoLog;
class InfoLogService
->setMessage($messageText)
->setLogTime()
->setCreatedAt();
+
+
+
// Валидация и сохранение лога в базе данных
if ($infoLog->validate() && $infoLog->save()) {
- // Форматируем сообщение для отправки в Telegram
- $telegramMessage = sprintf(
- "*Info Log Detected*\n\n" .
- "*File:* `%s`\n" .
- "*Line:* `%s`\n" .
- "*Message:*\n```%s```\n\n" .
- "*Context:*\n```%s```",
- $file ?? '<no file>',
- $line ?? '<no line>',
- $messageText,
- $context ?? '<no context>'
- );
+ // Проверяем, нужно ли отправлять сообщение в Telegram
+ if (!self::shouldSendToTelegram($file, $line, $messageText, $context)) {
+ return; // Пропускаем отправку, если уже есть записи за текущую дату
+ }
+
+ // Формируем сообщение для отправки в Telegram
+ $telegramMessage = "⚠️*Сообщение из InfoLog*⚠️\n\n";
+
+ // Добавляем строки в сообщение только если параметры присутствуют
+ if ($file) {
+ $telegramMessage .= "*File:*\n```" . self::escapeMarkdown($file) . "```\n\n";
+ }
+
+ if ($line) {
+ $telegramMessage .= "*Line:*\n```" . self::escapeMarkdown($line) . "```\n\n";
+ }
+
+ if ($messageText) {
+ $telegramMessage .= "*Сообщение:*\n```log" . self::escapeMarkdown($messageText) . "```\n\n";
+ }
+
+ if ($context) {
+ $telegramMessage .= "*Context:*\n```log" . self::escapeMarkdown($context) . "```\n\n";
+ }
+
+
+ $disableNotification = false;
// Отправляем сообщение в Telegram
- TelegramService::sendErrorToTelegramMessage($telegramMessage);
+ TelegramService::sendErrorToTelegramMessage($telegramMessage, $disableNotification);
}
}
+
+ // Метод для экранирования символов MarkdownV2
+ private static function escapeMarkdown($text)
+ {
+ $specialChars = ['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!'];
+ foreach ($specialChars as $char) {
+ $text = str_replace($char, '\\' . $char, $text);
+ }
+ return $text;
+ }
+
+
+ // Метод для проверки, существуют ли подобные записи в InfoLog за текущую дату
+ private static function shouldSendToTelegram($file, $line, $messageText, $context)
+ {
+ $currentDate = date('Y-m-d');
+
+ // Выполняем запрос, чтобы найти записи с такими же значениями за текущую дату
+ $count = InfoLog::find()
+ ->where([
+ 'file' => $file,
+ 'line' => $line,
+ 'message' => $messageText,
+ 'context' => $context,
+ ])
+ ->andWhere(['>=', 'created_at', new Expression("DATE('$currentDate')")])
+ ->count();
+
+ return $count <= 1; // Возвращаем true, только если нет более одной записи
+ }
+
}
\ No newline at end of file
$apiErrorLog->ip = Yii::$app->request->remoteIP ?? '<no ip>';
$apiErrorLog->save();
- // Форматируем сообщение об ошибке для Telegram с использованием MarkdownV2
- $errorMessage = sprintf(
- "*API Error Detected*\n\n" .
- "*URL:*\n```%s```\n\n" .
- "*Created At:*\n```%s```\n\n" .
- "*IP:*\n```%s```\n\n" .
- "*Payload:*\n```json\n%s```",
- $apiErrorLog->url,
- $apiErrorLog->created_at,
- $apiErrorLog->ip,
- $jsonString
- );
-
- // Отправляем сообщение об ошибке в Telegram
- TelegramService::sendErrorToTelegramMessage($errorMessage);
+ // Проверка на наличие аналогичной записи перед созданием новой и отправкой в Telegram
+ if (!self::shouldSendToTelegram($hash_input, $jsonString)) {
+ return; // Пропускаем отправку, если уже есть аналогичная запись за текущую дату
+ }
+
+
+ // Форматирование сообщения об ошибке с условным добавлением строк
+ $errorMessage = "⚠️*Ошибка API Обнаружена*⚠️\n\n";
+
+ // Добавляем строки только если параметры присутствуют
+ if ($url = $apiErrorLog->url) {
+ $errorMessage .= "*URL:*\n```" . self::escapeMarkdown($url) . "```\n\n";
+ }
+
+ if ($createdAt = $apiErrorLog->created_at) {
+ $errorMessage .= "*Created At:*\n```" . $createdAt . "```\n\n";
+ }
+
+ if ($ip = $apiErrorLog->ip) {
+ $errorMessage .= "*IP:*\n```" . self::escapeMarkdown($ip) . "```\n\n";
+ }
+
+ if ($jsonString) {
+ $errorMessage .= "*Payload:*\n```json\n" . $jsonString . "```\n\n";
+ }
+
+
+ $disableNotification = false;
+ // Отправляем сообщение об ошибке в Telegram через TelegramService
+ TelegramService::sendErrorToTelegramMessage($errorMessage, $disableNotification);
} else {
$h->save();
}
}
+
+
+// Метод для экранирования символов MarkdownV2
+ private static function escapeMarkdown($text)
+ {
+ $specialChars = ['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!'];
+ foreach ($specialChars as $char) {
+ $text = str_replace($char, '\\' . $char, $text);
+ }
+ return $text;
+ }
+
+
+// Метод для проверки наличия аналогичных записей в ApiErrorLog за текущую дату
+ private static function shouldSendToTelegram($hash_input, $jsonString)
+ {
+ $startOfDay = strtotime('today'); // Метка времени для начала текущего дня
+
+ // Запрос в таблицу ApiErrorLog для поиска аналогичных записей за текущую дату
+ $count = ApiErrorLog::find()
+ ->where([
+ 'hash_input' => $hash_input,
+ 'url' => Yii::$app->request->url ?? '<no url>',
+ 'ip' => Yii::$app->request->remoteIP ?? '<no ip>',
+ 'payload' => $jsonString,
+ ])
+ ->andWhere(['>=', 'created_at', date('Y-m-d H:i:s', $startOfDay)]) // Фильтр по началу текущего дня
+ ->count();
+
+ return $count <= 1; // Отправляем только если нет более одной записи
+ }
+
+
}
return $client->request('GET', $url);
}
-public static function sendErrorToTelegramMessage($message)
+public static function sendErrorToTelegramMessage($message,$disableNotification)
{
$botToken = self::TELEGRAM_API_URL;
$chatId = self::CHAT_CHANNEL_ID;
$apiURL = "https://api.telegram.org/bot{$botToken}/sendMessage";
-
+ //$message = self::escapeMarkdown($message);
$client = new Client();
try {
$client->post($apiURL, [
'json' => [
'chat_id' => $chatId,
'text' => $message,
- 'parse_mode' => 'MarkdownV2'
+ 'parse_mode' => 'MarkdownV2',
+ 'disable_notification' => $disableNotification,
],
]);
} catch (\Exception $e) {
}
+ // Метод для экранирования символов MarkdownV2
+ private static function escapeMarkdown($text)
+ {
+ // Экранирование символов, зарезервированных в MarkdownV2
+ $specialChars = ['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!'];
+
+ foreach ($specialChars as $char) {
+ // Проверяем, что символ не находится внутри кода
+ $text = preg_replace_callback('/(?<!`)([' . preg_quote($char) . '])(?!`)/', function ($matches) {
+ return '\\' . $matches[1];
+ }, $text);
+ }
+ return $text;
+ }
+
+
}
\ No newline at end of file
namespace yii_app\services;
+use yii\log\Logger;
use yii\log\Target;
use Yii;
use GuzzleHttp\Client;
public $botToken = "8063257458:AAGnMf4cxwJWlYLF1wS_arn4PrOaLs9ERQQ";
public $chatId ="-1001861631125";
+
+
public function export()
{
$apiURL = 'https://api.telegram.org/bot' . $this->botToken . '/sendMessage';
--- /dev/null
+<?php
+
+use yii\helpers\Html;
+
+/* @var $this yii\web\View */
+
+$this->title = 'Тестирование логов';
+?>
+
+<div class="site-index p-4">
+ <h1><?= $this->title ?></h1>
+
+ <div class="log-buttons">
+ <?= Html::a('Отправить API Error Log', ['test-log/generate-log', 'type' => 'api_error'], ['class' => 'btn btn-danger']) ?>
+ <?= Html::a('Отправить Error Log', ['test-log/generate-log', 'type' => 'error_log'], ['class' => 'btn btn-warning']) ?>
+ <?= Html::a('Отправить Info Log', ['test-log/generate-log', 'type' => 'info_log'], ['class' => 'btn btn-info']) ?>
+ </div>
+</div>
\ No newline at end of file