feat(log):添加日志硬删除按钮,防止频繁错误影响数据库

This commit is contained in:
qian committed 2026-08-02 00:56:03 +08:00
1 parent 3a6a71b9e7
commit a24f9d29db
13 files changed
+157 -6

No files matched your search

+2
View File
@@ -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)
+10
View File
@@ -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
View File
@@ -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)
}
+15
View File
@@ -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
+40
View File
@@ -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)
+8
View File
@@ -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
}
+2
View File
@@ -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"},
+5
View File
@@ -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 管理后台反馈列表
+1
View File
@@ -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>
+27 -1
View File
@@ -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>