feat: maintenance tasks, logs

- add interval for periodic maintenance tasks
- add cache prune periodic task
- use rotating handler for monolog
- update UI logs component
- correctly reset cache from admin
- increase cache timeout for tg data
- fix UI errors in admin
This commit is contained in:
2025-11-20 09:07:33 +03:00
parent 984d4d7ac3
commit ae9771dec4
15 changed files with 170 additions and 60 deletions

View File

@@ -1,16 +1,13 @@
<template>
<textarea v-text="rows" rows="40" class="tw:w-full"/>
<textarea v-text="logs.lines" rows="40" class="tw:w-full" readonly/>
</template>
<script setup>
import {onMounted, ref} from "vue";
import {apiGet} from "@/utils/http.js";
import {onMounted} from "vue";
import {useLogsStore} from "@/stores/logs.js";
const rows = ref('');
onMounted(async () => {
const response = await apiGet('getLogs');
rows.value = response.data;
});
const logs = useLogsStore();
onMounted(async () => logs.fetchLogsFromServer());
</script>
<style scoped>

View File

@@ -65,27 +65,30 @@
</div>
</div>
<div class="tw:mt-6 tw:lg:mt-0 tw:flex tw:items-center tw:gap-4">
<ButtonGroup>
<ResetCacheBtn/>
</ButtonGroup>
<div class="btn-group">
<a
class="btn btn-primary"
:class="{'disabled': (tgMe?.result?.has_main_web_app !== true)}"
rounded
:href="`https://t.me/${tgMe?.result?.username}?startapp`"
<ButtonGroup>
<Button
icon="fa fa-play"
v-tooltip.top="(tgMe?.result?.has_main_web_app !== true) ? 'Вы не привязали Telegram Mini App к боту.' : 'Открыть Telegram магазин'"
as="a"
target="_blank"
:title="(tgMe?.result?.has_main_web_app !== true) ? 'Вы не привязали Telegram Mini App к боту.' : 'Открыть Telegram магазин'"
>
<i class="fa fa-play"></i>
</a>
<a class="btn btn-default" target="_blank" href="https://telecart-labs.github.io/docs/" title="Документация по модулю TeleCart">
<i class="fa fa-book"></i>
</a>
<a class="btn btn-default" target="_blank" href="https://t.me/ocstore3" title="Официальная Telegram группа модуля TeleCart">
<i class="fa fa-group"></i>
</a>
</div>
:href="`https://t.me/${tgMe?.result?.username}?startapp`"
/>
<Button
icon="fa fa-book"
v-tooltip.top="'Документация по модулю TeleCart'"
as="a"
target="_blank"
href="https://telecart-labs.github.io/docs/"
/>
<Button
icon="fa fa-group"
v-tooltip.top="'Официальная Telegram группа модуля TeleCart'"
as="a"
target="_blank"
href="https://t.me/ocstore3"
/>
</ButtonGroup>
</div>
</div>
</div>
@@ -98,6 +101,7 @@ import {onMounted, ref} from "vue";
import OcImagePicker from "@/components/OcImagePicker.vue";
import {apiGet} from "@/utils/http.js";
import ResetCacheBtn from "@/components/Form/ResetCacheBtn.vue";
import {Button, ButtonGroup} from "primevue";
const settings = useSettingsStore();
const stats = useStatsStore();

View File

@@ -0,0 +1,16 @@
import {defineStore} from "pinia";
import {apiGet} from "@/utils/http.js";
export const useLogsStore = defineStore('logs', {
state: () => ({
lines: '',
}),
actions: {
async fetchLogsFromServer() {
if (this.lines) return;
const response = await apiGet('getLogs');
this.lines = response.data;
},
},
});

View File

@@ -2,7 +2,7 @@
use Bastion\ApplicationFactory;
use Cart\User;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
use Openguru\OpenCartFramework\Application;
use Openguru\OpenCartFramework\Http\Response as HttpResponse;
@@ -252,7 +252,7 @@ class ControllerExtensionModuleTgshop extends Controller
'language_id' => (int) $this->config->get('config_language_id'),
],
'logs' => [
'path' => DIR_LOGS . '/telecart.log',
'path' => DIR_LOGS,
],
'database' => [
'host' => DB_HOSTNAME,
@@ -298,10 +298,9 @@ class ControllerExtensionModuleTgshop extends Controller
{
$log = new Logger('TeleCart_Admin');
$log->pushHandler(
new StreamHandler(
DIR_LOGS . '/telecart.log',
$debug ? Logger::DEBUG : Logger::INFO,
)
new RotatingFileHandler(
DIR_LOGS . '/telecart.log', 14, $debug ? Logger::DEBUG : Logger::INFO
),
);
return $log;

View File

@@ -5,7 +5,7 @@ use App\ApplicationFactory;
use Cart\Cart;
use Cart\Currency;
use Cart\Tax;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
use Openguru\OpenCartFramework\Http\Response as HttpResponse;
use Openguru\OpenCartFramework\ImageTool\ImageTool;
@@ -154,10 +154,7 @@ class ControllerExtensionTgshopHandle extends Controller
{
$log = new Logger($app);
$log->pushHandler(
new StreamHandler(
DIR_LOGS . '/telecart.log',
$appDebug ? Logger::DEBUG : Logger::INFO,
)
new RotatingFileHandler(DIR_LOGS . '/telecart.log', 14, $appDebug ? Logger::DEBUG : Logger::INFO),
);
return $log;

View File

@@ -19,9 +19,15 @@ class LogsHandler
public function getLogs(): JsonResponse
{
$logsPath = $this->settings->get('logs.path');
$data = [];
$data = implode(PHP_EOL, $this->readLastLogsRows($logsPath));
$logsPath = $this->findLastLogsFileInDir(
$this->settings->get('logs.path')
);
if ($logsPath) {
$data = implode(PHP_EOL, $this->readLastLogsRows($logsPath, 100));
}
return new JsonResponse(compact('data'));
}
@@ -54,6 +60,13 @@ class LogsHandler
$linesArray = explode("\n", $chunk);
return array_reverse(array_slice($linesArray, -$lines));
return array_slice($linesArray, -$lines);
}
private function findLastLogsFileInDir(string $dir): ?string
{
$files = glob($dir . '/telecart-*.log');
return $files ? end($files) : null;
}
}

View File

@@ -12,6 +12,7 @@ use Openguru\OpenCartFramework\Http\JsonResponse;
use Openguru\OpenCartFramework\Http\Request;
use Openguru\OpenCartFramework\Http\Response;
use Openguru\OpenCartFramework\Support\Arr;
use Psr\Log\LoggerInterface;
class SettingsHandler
{
@@ -19,17 +20,20 @@ class SettingsHandler
private Settings $settings;
private SettingsService $settingsUpdateService;
private CacheInterface $cache;
private LoggerInterface $logger;
public function __construct(
BotTokenConfigurator $botTokenConfigurator,
Settings $settings,
SettingsService $settingsUpdateService,
CacheInterface $cache
CacheInterface $cache,
LoggerInterface $logger
) {
$this->botTokenConfigurator = $botTokenConfigurator;
$this->settings = $settings;
$this->settingsUpdateService = $settingsUpdateService;
$this->cache = $cache;
$this->logger = $logger;
}
public function configureBotToken(Request $request): JsonResponse
@@ -78,7 +82,9 @@ class SettingsHandler
public function resetCache(): JsonResponse
{
$this->cache->prune();
$this->cache->clear();
$this->logger->info('Cache cleared manually.');
return new JsonResponse([], Response::HTTP_ACCEPTED);
}

View File

@@ -131,7 +131,7 @@ class TelegramHandler
if (! $data) {
$data = $this->telegramService->exec('getMe');
$this->cache->set('tg_me_info', $data, 60);
$this->cache->set('tg_me_info', $data, 60 * 5);
}
return new JsonResponse(compact('data'));

View File

@@ -0,0 +1,30 @@
<?php
namespace Bastion\Tasks;
use DateInterval;
use Openguru\OpenCartFramework\Cache\CacheInterface;
use Openguru\OpenCartFramework\MaintenanceTasks\BaseMaintenanceTask;
use Psr\Log\LoggerInterface;
class CachePruneTask extends BaseMaintenanceTask
{
private CacheInterface $cache;
public function __construct(LoggerInterface $logger, CacheInterface $cache)
{
parent::__construct($logger);
$this->cache = $cache;
}
public function handle(): void
{
$this->cache->prune();
}
public function interval(): ?DateInterval
{
return new DateInterval('P1D');
}
}

View File

@@ -2,12 +2,13 @@
namespace Bastion\Tasks;
use DateInterval;
use Exception;
use JsonException;
use Openguru\OpenCartFramework\MaintenanceTasks\BaseMaintenanceTask;
use RuntimeException;
class CleanUpOldAssets extends BaseMaintenanceTask
class CleanUpOldAssetsTask extends BaseMaintenanceTask
{
public function handle(): void
{
@@ -61,8 +62,12 @@ class CleanUpOldAssets extends BaseMaintenanceTask
} catch (JsonException $e) {
$this->logger->error('Ошибка декодирования файла manifest.json: ' . $e->getMessage());
} catch (Exception $e) {
$this->logger->error('Ошибка удаления старых assets: ' . $e->getMessage());
$this->logger->logException($e);
$this->logger->error('Ошибка удаления старых assets: ' . $e->getMessage(), ['exception' => $e]);
}
}
public function interval(): ?DateInterval
{
return new DateInterval('PT1H');
}
}

View File

@@ -1,9 +1,11 @@
<?php
use Bastion\Tasks\CleanUpOldAssets;
use Bastion\Tasks\CachePruneTask;
use Bastion\Tasks\CleanUpOldAssetsTask;
return [
'tasks' => [
CleanUpOldAssets::class,
CleanUpOldAssetsTask::class,
CachePruneTask::class,
],
];

View File

@@ -2,6 +2,7 @@
namespace Openguru\OpenCartFramework\MaintenanceTasks;
use DateInterval;
use Psr\Log\LoggerInterface;
abstract class BaseMaintenanceTask implements MaintenanceTaskInterface
@@ -12,4 +13,9 @@ abstract class BaseMaintenanceTask implements MaintenanceTaskInterface
{
$this->logger = $logger;
}
public function interval(): ?DateInterval
{
return null;
}
}

View File

@@ -2,7 +2,11 @@
namespace Openguru\OpenCartFramework\MaintenanceTasks;
use DateInterval;
interface MaintenanceTaskInterface
{
public function handle(): void;
public function interval(): ?DateInterval;
}

View File

@@ -2,22 +2,27 @@
namespace Openguru\OpenCartFramework\MaintenanceTasks;
use Psr\Log\LoggerInterface;
use Carbon\Carbon;
use Openguru\OpenCartFramework\Cache\CacheInterface;
use Openguru\OpenCartFramework\Migrations\MigratorService;
use Psr\Log\LoggerInterface;
class MaintenanceTasksService
{
private MigratorService $migrator;
private LoggerInterface $logger;
private CacheInterface $cache;
private array $maintenanceTasks;
public function __construct(
MigratorService $migrator,
LoggerInterface $logger,
CacheInterface $cache,
array $maintenanceTasks = []
) {
$this->migrator = $migrator;
$this->logger = $logger;
$this->cache = $cache;
$this->maintenanceTasks = $maintenanceTasks;
}
@@ -30,9 +35,11 @@ class MaintenanceTasksService
private function performMaintenanceTasks(): void
{
foreach ($this->maintenanceTasks as $maintenanceTask) {
$startTime = microtime(true);
/** @var MaintenanceTaskInterface $instance */
$instance = $maintenanceTask();
if ($this->shouldPerformTask($instance)) {
$startTime = microtime(true);
$instance->handle();
$endTime = microtime(true);
$this->logger->info(
@@ -45,3 +52,25 @@ class MaintenanceTasksService
}
}
}
private function shouldPerformTask(MaintenanceTaskInterface $task): bool
{
$cacheKey = 'maintenance_tasks.' . md5(get_class($task));
$lastExecuted = $this->cache->get($cacheKey);
if (!$lastExecuted) {
$this->cache->set($cacheKey, Carbon::now());
return true;
}
$last = Carbon::parse($lastExecuted);
$next = $last->copy()->add($task->interval());
if (Carbon::now()->gte($next)) {
$this->cache->set($cacheKey, Carbon::now());
return true;
}
return false;
}
}

View File

@@ -2,6 +2,7 @@
namespace Openguru\OpenCartFramework\MaintenanceTasks;
use Openguru\OpenCartFramework\Cache\CacheInterface;
use Openguru\OpenCartFramework\Container\Container;
use Openguru\OpenCartFramework\Container\ServiceProvider;
use Psr\Log\LoggerInterface;
@@ -19,6 +20,7 @@ class MaintenanceTasksServiceProvider extends ServiceProvider
return new MaintenanceTasksService(
$container->get(MigratorService::class),
$container->get(LoggerInterface::class),
$container->get(CacheInterface::class),
$tasks,
);
});