feat(log):添加日志硬删除按钮,防止频繁错误影响数据库
This commit is contained in:
1 parent
3a6a71b9e7
commit
a24f9d29db
13 files changed
+157
-6
No files matched your search
@@ -104,6 +104,7 @@ type IAdminV1 interface {
|
||||
ErrorLogResolve(ctx context.Context, req *v1.ErrorLogResolveReq) (res *v1.ErrorLogResolveRes, err error)
|
||||
ErrorLogBatchResolve(ctx context.Context, req *v1.ErrorLogBatchResolveReq) (res *v1.ErrorLogBatchResolveRes, err error)
|
||||
ErrorLogStats(ctx context.Context, req *v1.ErrorLogStatsReq) (res *v1.ErrorLogStatsRes, err error)
|
||||
ErrorLogClear(ctx context.Context, req *v1.ErrorLogClearReq) (res *v1.ErrorLogClearRes, err error)
|
||||
FeedbackListAll(ctx context.Context, req *v1.FeedbackListAllReq) (res *v1.FeedbackListAllRes, err error)
|
||||
FeedbackReply(ctx context.Context, req *v1.FeedbackReplyReq) (res *v1.FeedbackReplyRes, err error)
|
||||
FeedbackUpdateStatus(ctx context.Context, req *v1.FeedbackUpdateStatusReq) (res *v1.FeedbackUpdateStatusRes, err error)
|
||||
@@ -164,6 +165,7 @@ type IAdminV1 interface {
|
||||
AlertEventList(ctx context.Context, req *v1.AlertEventListReq) (res *v1.AlertEventListRes, err error)
|
||||
AlertEventAcknowledge(ctx context.Context, req *v1.AlertEventAcknowledgeReq) (res *v1.AlertEventAcknowledgeRes, err error)
|
||||
AlertEventResolve(ctx context.Context, req *v1.AlertEventResolveReq) (res *v1.AlertEventResolveRes, err error)
|
||||
AlertEventClear(ctx context.Context, req *v1.AlertEventClearReq) (res *v1.AlertEventClearRes, err error)
|
||||
TemplateList(ctx context.Context, req *v1.TemplateListReq) (res *v1.TemplateListRes, err error)
|
||||
TemplateGet(ctx context.Context, req *v1.TemplateGetReq) (res *v1.TemplateGetRes, err error)
|
||||
TemplateUpdate(ctx context.Context, req *v1.TemplateUpdateReq) (res *v1.TemplateUpdateRes, err error)
|
||||
|
||||
@@ -61,3 +61,13 @@ type ErrorLogStatsReq struct {
|
||||
type ErrorLogStatsRes struct {
|
||||
Data map[string]any `json:"data"`
|
||||
}
|
||||
|
||||
// ErrorLogClearReq 清空全部错误日志(硬删除,用于错误大量堆积时快速释放数据库空间)
|
||||
type ErrorLogClearReq struct {
|
||||
g.Meta `path:"/error-logs/clear" method:"delete" mime:"json" tags:"管理后台-系统错误" summary:"清空错误日志(硬删除全部)"`
|
||||
}
|
||||
|
||||
// ErrorLogClearRes 清空错误日志响应
|
||||
type ErrorLogClearRes struct {
|
||||
Deleted int64 `json:"deleted" dc:"删除的记录数"`
|
||||
}
|
||||
+13
-3
@@ -24,9 +24,9 @@ type MonitorTrafficRes struct {
|
||||
|
||||
type MonitorTrafficFlowReq struct {
|
||||
g.Meta `path:"/monitor/traffic-flow" method:"get" mime:"json" tags:"管理后台-监控" summary:"流量流向桑基图"`
|
||||
StartDate string `json:"start_date" in:"query"` // YYYY-MM-DD,缺省=近30天起点
|
||||
EndDate string `json:"end_date" in:"query"` // YYYY-MM-DD,缺省=今天
|
||||
Metric string `json:"metric" in:"query" d:"cost"` // cost|tokens|requests,桑基边权重指标
|
||||
StartDate string `json:"start_date" in:"query"` // YYYY-MM-DD,缺省=近30天起点
|
||||
EndDate string `json:"end_date" in:"query"` // YYYY-MM-DD,缺省=今天
|
||||
Metric string `json:"metric" in:"query" d:"cost"` // cost|tokens|requests,桑基边权重指标
|
||||
}
|
||||
|
||||
type MonitorTrafficFlowRes struct {
|
||||
@@ -242,3 +242,13 @@ type AlertEventResolveReq struct {
|
||||
}
|
||||
|
||||
type AlertEventResolveRes struct{}
|
||||
|
||||
// AlertEventClearReq 清空全部告警记录(硬删除,同时清理 Redis 中的触发状态)
|
||||
type AlertEventClearReq struct {
|
||||
g.Meta `path:"/alert/events/clear" method:"delete" mime:"json" tags:"管理后台-告警" summary:"清空告警记录(硬删除全部)"`
|
||||
}
|
||||
|
||||
// AlertEventClearRes 清空告警记录响应
|
||||
type AlertEventClearRes struct {
|
||||
Deleted int64 `json:"deleted" dc:"删除的记录数"`
|
||||
}
|
||||
@@ -22,3 +22,6 @@ func (c *ControllerV1) ErrorLogBatchResolve(ctx context.Context, req *v1.ErrorLo
|
||||
func (c *ControllerV1) ErrorLogStats(ctx context.Context, req *v1.ErrorLogStatsReq) (res *v1.ErrorLogStatsRes, err error) {
|
||||
return service.Admin().ErrorLogStats(ctx, req)
|
||||
}
|
||||
func (c *ControllerV1) ErrorLogClear(ctx context.Context, req *v1.ErrorLogClearReq) (res *v1.ErrorLogClearRes, err error) {
|
||||
return service.Admin().ErrorLogClear(ctx, req)
|
||||
}
|
||||
@@ -67,3 +67,6 @@ func (c *ControllerV1) MonitorModelPerformance(ctx context.Context, req *v1.Moni
|
||||
func (c *ControllerV1) MonitorDispatch(ctx context.Context, req *v1.MonitorDispatchReq) (res *v1.MonitorDispatchRes, err error) {
|
||||
return service.Monitor().Dispatch(ctx, req)
|
||||
}
|
||||
func (c *ControllerV1) AlertEventClear(ctx context.Context, req *v1.AlertEventClearReq) (res *v1.AlertEventClearRes, err error) {
|
||||
return service.Monitor().AlertEventClear(ctx, req)
|
||||
}
|
||||
@@ -129,6 +129,21 @@ func (s *sAdmin) ErrorLogBatchResolve(ctx context.Context, req *v1.ErrorLogBatch
|
||||
return &v1.ErrorLogBatchResolveRes{}, nil
|
||||
}
|
||||
|
||||
// ErrorLogClear 硬删除全部错误日志。
|
||||
// 系统报错大量堆积时用于快速释放数据库空间:用 TRUNCATE 而非逐行 DELETE,
|
||||
// 秒级清空并立即归还磁盘空间(DELETE 需等 VACUUM 回收)。TRUNCATE 不返回行数,
|
||||
// 故先取删除前条数作为反馈。表无数据库级外键,TRUNCATE 安全。
|
||||
func (s *sAdmin) ErrorLogClear(ctx context.Context, _ *v1.ErrorLogClearReq) (*v1.ErrorLogClearRes, error) {
|
||||
count, err := dao.SysErrorLogs.Ctx(ctx).Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := g.DB().Ctx(ctx).Exec(ctx, "TRUNCATE TABLE sys_error_logs RESTART IDENTITY"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &v1.ErrorLogClearRes{Deleted: int64(count)}, nil
|
||||
}
|
||||
|
||||
// ErrorLogStats returns error log statistics.
|
||||
func (s *sAdmin) ErrorLogStats(ctx context.Context, _ *v1.ErrorLogStatsReq) (*v1.ErrorLogStatsRes, error) {
|
||||
// Total unresolved
|
||||
|
||||
@@ -200,6 +200,46 @@ func GetAlertStats(ctx context.Context) (map[string]any, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ClearAlertEvents 硬删除全部告警事件,并清理关联的 Redis 触发状态。
|
||||
// 告警事件表持续追加,异常频发时会迅速膨胀拖累数据库;TRUNCATE 秒级清空并立即归还
|
||||
// 磁盘空间(DELETE 需等 VACUUM 回收)。表无数据库级外键,TRUNCATE 安全。
|
||||
func ClearAlertEvents(ctx context.Context) (int64, error) {
|
||||
count, err := dao.OpsAlertEvents.Ctx(ctx).Count()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := g.DB().Ctx(ctx).Exec(ctx, "TRUNCATE TABLE ops_alert_events RESTART IDENTITY"); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := clearAlertFiringState(ctx); err != nil {
|
||||
g.Log().Warningf(ctx, "clear alert events: reset firing state: %v", err)
|
||||
}
|
||||
return int64(count), nil
|
||||
}
|
||||
|
||||
// clearAlertFiringState 清理 Redis 中所有告警规则的触发状态(ops:alert:firing:{ruleID})。
|
||||
// 事件表清空后,若引擎仍持有旧 eventID 的 firing 状态,会持续尝试 auto-resolve 一个
|
||||
// 不存在的记录(产生 404 告警日志),且同一规则即便继续越界也不再生成新事件。
|
||||
// 触发状态键按 rule_id 命名,遍历规则表逐个 DEL 即可;已删除规则残留的键无引擎读取、1h 自愈。
|
||||
func clearAlertFiringState(ctx context.Context) error {
|
||||
redis := g.Redis()
|
||||
var ruleIDs []struct {
|
||||
Id int64 `json:"id"`
|
||||
}
|
||||
if err := dao.OpsAlertRules.Ctx(ctx).Fields("id").Scan(&ruleIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, r := range ruleIDs {
|
||||
if r.Id <= 0 {
|
||||
continue
|
||||
}
|
||||
if _, err := redis.Do(ctx, "DEL", fmt.Sprintf("ops:alert:firing:%d", r.Id)); err != nil {
|
||||
g.Log().Warningf(ctx, "clear alert firing state for rule %d: %v", r.Id, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendTestAlert sends a test notification for a given rule.
|
||||
func SendTestAlert(ctx context.Context, ruleID int64) error {
|
||||
rule, err := GetAlertRule(ctx, ruleID)
|
||||
|
||||
@@ -328,3 +328,11 @@ func (s *sMonitor) ResolveAlert(ctx context.Context, req *v1.AlertEventResolveRe
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *sMonitor) AlertEventClear(ctx context.Context, _ *v1.AlertEventClearReq) (*v1.AlertEventClearRes, error) {
|
||||
deleted, err := ClearAlertEvents(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &v1.AlertEventClearRes{Deleted: deleted}, nil
|
||||
}
|
||||
@@ -216,10 +216,12 @@ var adminPermissionRules = []permissionRule{
|
||||
{method: "POST", prefix: "/api/admin/alert/rules/", suffix: "/test", perm: "monitor:edit"},
|
||||
{method: "GET", path: "/api/admin/alert/events", perm: "monitor:view"},
|
||||
{method: "PUT", prefix: "/api/admin/alert/events/", perm: "monitor:edit"},
|
||||
{method: "DELETE", path: "/api/admin/alert/events/clear", perm: "monitor:edit"},
|
||||
{method: "GET", path: "/api/admin/error-logs", perm: "monitor:view"},
|
||||
{method: "GET", prefix: "/api/admin/error-logs/", perm: "monitor:view"},
|
||||
{method: "PUT", prefix: "/api/admin/error-logs/", perm: "monitor:edit"},
|
||||
{method: "PUT", path: "/api/admin/error-logs/batch-resolve", perm: "monitor:edit"},
|
||||
{method: "DELETE", path: "/api/admin/error-logs/clear", perm: "monitor:edit"},
|
||||
{method: "GET", path: "/api/admin/error-logs/stats", perm: "monitor:view"},
|
||||
{method: "GET", path: "/api/admin/cron-jobs", perm: "system:view"},
|
||||
{method: "POST", prefix: "/api/admin/cron-jobs/", suffix: "/trigger", perm: "system:edit"},
|
||||
|
||||
@@ -202,6 +202,11 @@ type (
|
||||
ErrorLogResolve(ctx context.Context, req *v1.ErrorLogResolveReq) (*v1.ErrorLogResolveRes, error)
|
||||
// ErrorLogBatchResolve marks multiple error logs as resolved.
|
||||
ErrorLogBatchResolve(ctx context.Context, req *v1.ErrorLogBatchResolveReq) (*v1.ErrorLogBatchResolveRes, error)
|
||||
// ErrorLogClear 硬删除全部错误日志。
|
||||
// 系统报错大量堆积时用于快速释放数据库空间:用 TRUNCATE 而非逐行 DELETE,
|
||||
// 秒级清空并立即归还磁盘空间(DELETE 需等 VACUUM 回收)。TRUNCATE 不返回行数,
|
||||
// 故先取删除前条数作为反馈。表无数据库级外键,TRUNCATE 安全。
|
||||
ErrorLogClear(ctx context.Context, _ *v1.ErrorLogClearReq) (*v1.ErrorLogClearRes, error)
|
||||
// ErrorLogStats returns error log statistics.
|
||||
ErrorLogStats(ctx context.Context, _ *v1.ErrorLogStatsReq) (*v1.ErrorLogStatsRes, error)
|
||||
// ListAllFeedbacks 管理后台反馈列表
|
||||
|
||||
@@ -34,6 +34,7 @@ type (
|
||||
AlertEventList(ctx context.Context, req *v1.AlertEventListReq) (*v1.AlertEventListRes, error)
|
||||
AcknowledgeAlert(ctx context.Context, req *v1.AlertEventAcknowledgeReq) (*v1.AlertEventAcknowledgeRes, error)
|
||||
ResolveAlert(ctx context.Context, req *v1.AlertEventResolveReq) (*v1.AlertEventResolveRes, error)
|
||||
AlertEventClear(ctx context.Context, _ *v1.AlertEventClearReq) (*v1.AlertEventClearRes, error)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, onUnmounted, h } from 'vue'
|
||||
import { Message, Tag, Button, Space } from '@arco-design/web-vue'
|
||||
import { Message, Modal, Tag, Button, Space } from '@arco-design/web-vue'
|
||||
import type { TableColumnData } from '@arco-design/web-vue'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import TableStats from '@/components/TableStats.vue'
|
||||
import request from '@/utils/request'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
|
||||
const loading = ref(false)
|
||||
const data = ref<any[]>([])
|
||||
@@ -145,6 +146,25 @@ function handlePageChange(page: number) {
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// 清空全部告警记录(硬删除):异常频发时事件表会迅速膨胀拖累数据库,需二次确认。
|
||||
function clearEvents() {
|
||||
Modal.confirm({
|
||||
title: '清空全部告警记录',
|
||||
content: '将硬删除全部告警记录(含已恢复的历史告警),此操作不可恢复,确定继续?',
|
||||
okText: '清空',
|
||||
okButtonProps: { status: 'danger' },
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
try {
|
||||
await request.delete('/admin/alert/events/clear')
|
||||
Message.success('告警记录已清空')
|
||||
fetchData()
|
||||
} catch {
|
||||
// error auto-shown by Axios interceptor
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
@@ -156,7 +176,13 @@ onUnmounted(() => {
|
||||
|
||||
<template>
|
||||
<div class="page-table">
|
||||
<PageHeader title="告警记录" description="查看和管理告警事件" />
|
||||
<PageHeader title="告警记录" description="查看和管理告警事件">
|
||||
<template #actions>
|
||||
<a-button v-if="hasPermission('monitor:edit')" status="danger" @click="clearEvents">
|
||||
清空记录
|
||||
</a-button>
|
||||
</template>
|
||||
</PageHeader>
|
||||
|
||||
<a-card :bordered="false" class="mb-4">
|
||||
<a-space>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, h } from 'vue'
|
||||
import { Message, Tag, Button, Space } from '@arco-design/web-vue'
|
||||
import { Message, Modal, Tag, Button, Space } from '@arco-design/web-vue'
|
||||
import type { TableColumnData } from '@arco-design/web-vue'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import TableStats from '@/components/TableStats.vue'
|
||||
import request from '@/utils/request'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
|
||||
const loading = ref(false)
|
||||
const data = ref<any[]>([])
|
||||
@@ -188,6 +189,28 @@ async function batchResolve() {
|
||||
}
|
||||
}
|
||||
|
||||
// 清空全部错误日志(硬删除):系统报错大量堆积时快速释放数据库空间,需二次确认。
|
||||
function clearLogs() {
|
||||
Modal.confirm({
|
||||
title: '清空全部错误日志',
|
||||
content: '将硬删除全部错误日志记录,此操作不可恢复。系统报错频繁时可使用该操作快速释放数据库空间,确定继续?',
|
||||
okText: '清空',
|
||||
okButtonProps: { status: 'danger' },
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
try {
|
||||
await request.delete('/admin/error-logs/clear')
|
||||
Message.success('错误日志已清空')
|
||||
selectedKeys.value = []
|
||||
fetchData()
|
||||
fetchStats()
|
||||
} catch {
|
||||
// error auto-shown by Axios interceptor
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function handleFilter() {
|
||||
pagination.current = 1
|
||||
fetchData()
|
||||
@@ -230,6 +253,9 @@ onMounted(() => {
|
||||
>
|
||||
批量处理 ({{ selectedKeys.length }})
|
||||
</a-button>
|
||||
<a-button v-if="hasPermission('monitor:edit')" status="danger" @click="clearLogs">
|
||||
清空日志
|
||||
</a-button>
|
||||
</template>
|
||||
</PageHeader>
|
||||
|
||||
|
||||
Reference in new issue
Block a user