Files
YMhut-box-C-/server/unified-management/internal/db/system_logs.go
T
QWQLwToo 7745e7a2d4
build-winui / winui (push) Has been cancelled
服务端媒体源导入/保存/客户端输出链路修复:支持 snake/camel、subcategories/sources,默认客户端可见,保存后发布兼容 media-types.json。
新增数据库同步 Job API、持久化状态、实时输出、最新任务恢复,以及系统日志聚合接口。
管理端优化:日志中心、运维实时状态框、同步输出自动滚动、仪表盘“输出”列、真实延迟空态、本地 favicon/avatar。
新增 server/unified-management/assets/favicon.ico 和 developer-avatar.png,并接好 /favicon.ico、/admin/favicon.ico、/setup/favicon.ico、/assets/*。
WinUI 随机放映室卡片优先显示子接口原始 Description。
Inno 安装器输出框改为选区末尾 + SendMessage 滚动到底部。
2026-06-29 22:28:58 +08:00

228 lines
6.6 KiB
Go

package db
import (
"encoding/json"
"fmt"
"sort"
"strings"
)
func (s *Store) ListSystemLogsPage(filters SystemLogFilters) (SystemLogPage, error) {
page := filters.Page
if page <= 0 {
page = 1
}
perPage := filters.PerPage
if perPage <= 0 {
perPage = 35
}
if perPage > 100 {
perPage = 100
}
items, err := s.collectSystemLogs(filters)
if err != nil {
return SystemLogPage{}, err
}
sort.SliceStable(items, func(i, j int) bool {
if items[i].CreatedAt == items[j].CreatedAt {
return items[i].ID > items[j].ID
}
return items[i].CreatedAt > items[j].CreatedAt
})
total := len(items)
start := (page - 1) * perPage
if start > total {
start = total
}
end := start + perPage
if end > total {
end = total
}
return SystemLogPage{Items: items[start:end], Total: total, Page: page, PerPage: perPage}, nil
}
func (s *Store) collectSystemLogs(filters SystemLogFilters) ([]SystemLogItem, error) {
items := []SystemLogItem{}
appendItems := func(category string, next []SystemLogItem, err error) error {
if err != nil {
return err
}
for _, item := range next {
if matchesSystemLog(filters, item, category) {
items = append(items, item)
}
}
return nil
}
logs, err := s.operationLogs()
if err := appendItems("operation", logs, err); err != nil {
return nil, err
}
logs, err = s.healthLogs()
if err := appendItems("health", logs, err); err != nil {
return nil, err
}
logs, err = s.clientCallLogs()
if err := appendItems("client", logs, err); err != nil {
return nil, err
}
logs, err = s.databaseSyncLogs()
if err := appendItems("database_sync", logs, err); err != nil {
return nil, err
}
logs, err = s.legacySyncLogs()
if err := appendItems("legacy_sync", logs, err); err != nil {
return nil, err
}
return items, nil
}
func (s *Store) operationLogs() ([]SystemLogItem, error) {
rows, err := s.query(`SELECT id, actor, type, target, message, ip, user_agent, created_at FROM audit_logs ORDER BY id DESC LIMIT 500`)
if err != nil {
return nil, err
}
defer rows.Close()
items := []SystemLogItem{}
for rows.Next() {
var item AuditLog
if err := rows.Scan(&item.ID, &item.Actor, &item.Type, &item.Target, &item.Message, &item.IP, &item.UserAgent, &item.CreatedAt); err != nil {
return nil, err
}
items = append(items, SystemLogItem{
ID: item.ID,
Category: "operation",
Type: item.Type,
Target: item.Target,
Status: firstNonEmpty(item.Actor, "system"),
Message: item.Message,
Detail: strings.TrimSpace(item.IP + " " + item.UserAgent),
CreatedAt: item.CreatedAt,
})
}
return items, rows.Err()
}
func (s *Store) healthLogs() ([]SystemLogItem, error) {
rows, err := s.query(`SELECT h.id, h.source_db_id, COALESCE(e.source_id, ''), COALESCE(e.name, ''), h.status, h.latency_ms, h.error, h.checked_at
FROM endpoint_health_checks h LEFT JOIN source_endpoints e ON e.id = h.source_db_id
ORDER BY h.id DESC LIMIT 500`)
if err != nil {
return nil, err
}
defer rows.Close()
items := []SystemLogItem{}
for rows.Next() {
var id, sourceDBID int64
var sourceID, name, status, message, checkedAt string
var latency int
if err := rows.Scan(&id, &sourceDBID, &sourceID, &name, &status, &latency, &message, &checkedAt); err != nil {
return nil, err
}
target := firstNonEmpty(sourceID, fmt.Sprintf("source#%d", sourceDBID))
items = append(items, SystemLogItem{
ID: id,
Category: "health",
Type: "endpoint.health",
Target: target,
Status: status,
Message: firstNonEmpty(name, target),
Detail: fmt.Sprintf("%dms %s", latency, message),
CreatedAt: checkedAt,
})
}
return items, rows.Err()
}
func (s *Store) clientCallLogs() ([]SystemLogItem, error) {
rows, err := s.query(`SELECT id, source_id, status, latency_ms, error, client, created_at FROM endpoint_call_logs ORDER BY id DESC LIMIT 500`)
if err != nil {
return nil, err
}
defer rows.Close()
items := []SystemLogItem{}
for rows.Next() {
var id int64
var sourceID, status, message, client, createdAt string
var latency int
if err := rows.Scan(&id, &sourceID, &status, &latency, &message, &client, &createdAt); err != nil {
return nil, err
}
items = append(items, SystemLogItem{
ID: id,
Category: "client",
Type: "endpoint.call",
Target: sourceID,
Status: status,
Message: message,
Detail: fmt.Sprintf("%dms %s", latency, client),
CreatedAt: createdAt,
})
}
return items, rows.Err()
}
func (s *Store) databaseSyncLogs() ([]SystemLogItem, error) {
rows, err := s.query(`SELECT id, direction, status, message, tables_json, started_at, finished_at FROM database_sync_jobs ORDER BY id DESC LIMIT 500`)
if err != nil {
return nil, err
}
defer rows.Close()
items := []SystemLogItem{}
for rows.Next() {
job, err := scanDatabaseSyncJob(rows)
if err != nil {
return nil, err
}
detail, _ := json.Marshal(map[string]any{"tables": job.Tables, "warnings": job.Warnings, "errors": job.Errors})
items = append(items, SystemLogItem{
ID: job.ID,
Category: "database_sync",
Type: job.Direction,
Target: directionLabel(job.Direction),
Status: job.Status,
Message: strings.Join(job.Output, "\n"),
Detail: string(detail),
CreatedAt: firstNonEmpty(job.FinishedAt, job.StartedAt),
})
}
return items, rows.Err()
}
func (s *Store) legacySyncLogs() ([]SystemLogItem, error) {
rows, err := s.query(`SELECT id, status, summary, stats_json, started_at, finished_at FROM legacy_sync_jobs ORDER BY id DESC LIMIT 500`)
if err != nil {
return nil, err
}
defer rows.Close()
items := []SystemLogItem{}
for rows.Next() {
var item LegacySyncJob
if err := rows.Scan(&item.ID, &item.Status, &item.Summary, &item.StatsJSON, &item.StartedAt, &item.FinishedAt); err != nil {
return nil, err
}
items = append(items, SystemLogItem{
ID: item.ID,
Category: "legacy_sync",
Type: "legacy.sync",
Target: "legacy",
Status: item.Status,
Message: item.Summary,
Detail: item.StatsJSON,
CreatedAt: firstNonEmpty(item.FinishedAt, item.StartedAt),
})
}
return items, rows.Err()
}
func matchesSystemLog(filters SystemLogFilters, item SystemLogItem, category string) bool {
if value := strings.TrimSpace(filters.Category); value != "" && value != category && value != item.Category {
return false
}
if value := strings.ToLower(strings.TrimSpace(filters.Query)); value != "" {
haystack := strings.ToLower(strings.Join([]string{item.Category, item.Type, item.Target, item.Status, item.Message, item.Detail, item.CreatedAt}, " "))
return strings.Contains(haystack, value)
}
return true
}