Files
AI-CS/backend/service/system_log_service.go
T
2026-04-02 14:55:06 +08:00

204 lines
5.0 KiB
Go

package service
import (
"encoding/json"
"fmt"
"strings"
"sync/atomic"
"time"
"github.com/2930134478/AI-CS/backend/models"
"github.com/2930134478/AI-CS/backend/repository"
)
type CreateSystemLogInput struct {
Level string
Category string
Event string
Source string
TraceID string
ConversationID *uint
UserID *uint
VisitorID *uint
Message string
Meta map[string]interface{}
Timestamp *time.Time
}
type QuerySystemLogsInput struct {
From string
To string
Level string
Category string
Event string
Source string
ConversationID *uint
Keyword string
Page int
PageSize int
}
type QuerySystemLogsResult struct {
Items []models.SystemLog `json:"items"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
}
// SystemLogService 结构化日志服务(查询 + 写入)。
type SystemLogService struct {
repo *repository.SystemLogRepository
minPersistLevel atomic.Int32 // 见 SYSTEM_LOG_MIN_LEVEL / 数据库覆盖;logRankNone 表示关闭全部落库
}
func NewSystemLogService(repo *repository.SystemLogRepository, minPersistLevel int) *SystemLogService {
s := &SystemLogService{repo: repo}
s.SetMinPersistLevelRank(minPersistLevel)
return s
}
// SetMinPersistLevelRank 运行时调整最低落库级别(线程安全)。
func (s *SystemLogService) SetMinPersistLevelRank(rank int) {
if s == nil {
return
}
s.minPersistLevel.Store(int32(rank))
}
// MinPersistLevelRank 当前生效的最低落库级别数值。
func (s *SystemLogService) MinPersistLevelRank() int {
if s == nil {
return logRankInfo
}
return int(s.minPersistLevel.Load())
}
func (s *SystemLogService) shouldPersistLevel(level string) bool {
if s == nil {
return false
}
cur := int(s.minPersistLevel.Load())
if cur == logRankNone {
return false
}
return logLevelRank(level) >= cur
}
// ShouldPersistLevel 供中间件等在组装大 payload 前判断,避免无谓开销。
func (s *SystemLogService) ShouldPersistLevel(level string) bool {
return s.shouldPersistLevel(strings.ToLower(strings.TrimSpace(level)))
}
func (s *SystemLogService) Create(input CreateSystemLogInput) error {
level := strings.ToLower(strings.TrimSpace(input.Level))
if level == "" {
level = "info"
}
if !s.shouldPersistLevel(level) {
return nil
}
category := strings.ToLower(strings.TrimSpace(input.Category))
if category == "" {
category = "system"
}
source := strings.ToLower(strings.TrimSpace(input.Source))
if source == "" {
source = "backend"
}
event := strings.TrimSpace(input.Event)
if event == "" {
event = "general"
}
message := strings.TrimSpace(input.Message)
if message == "" {
return fmt.Errorf("message 不能为空")
}
metaJSON := ""
if input.Meta != nil {
if b, err := json.Marshal(input.Meta); err == nil {
metaJSON = string(b)
}
}
ts := time.Now()
if input.Timestamp != nil {
ts = *input.Timestamp
}
item := &models.SystemLog{
Timestamp: ts,
Level: level,
Category: category,
Event: event,
Source: source,
TraceID: strings.TrimSpace(input.TraceID),
ConversationID: input.ConversationID,
UserID: input.UserID,
VisitorID: input.VisitorID,
Message: message,
MetaJSON: metaJSON,
}
return s.repo.Create(item)
}
func (s *SystemLogService) Query(input QuerySystemLogsInput) (*QuerySystemLogsResult, error) {
page := input.Page
if page <= 0 {
page = 1
}
pageSize := input.PageSize
if pageSize <= 0 {
pageSize = 50
}
if pageSize > 200 {
pageSize = 200
}
db := s.repo.DB().Model(&models.SystemLog{})
if input.From != "" {
if t, err := time.Parse("2006-01-02", input.From); err == nil {
db = db.Where("timestamp >= ?", t)
}
}
if input.To != "" {
if t, err := time.Parse("2006-01-02", input.To); err == nil {
db = db.Where("timestamp < ?", t.AddDate(0, 0, 1))
}
}
if v := strings.TrimSpace(input.Level); v != "" {
db = db.Where("level = ?", strings.ToLower(v))
}
if v := strings.TrimSpace(input.Category); v != "" {
db = db.Where("category = ?", strings.ToLower(v))
}
if v := strings.TrimSpace(input.Event); v != "" {
db = db.Where("event = ?", v)
}
if v := strings.TrimSpace(input.Source); v != "" {
db = db.Where("source = ?", strings.ToLower(v))
}
if input.ConversationID != nil {
db = db.Where("conversation_id = ?", *input.ConversationID)
}
if v := strings.TrimSpace(input.Keyword); v != "" {
like := "%" + v + "%"
db = db.Where("(message LIKE ? OR meta_json LIKE ? OR trace_id LIKE ?)", like, like, like)
}
var total int64
if err := db.Count(&total).Error; err != nil {
return nil, err
}
items := make([]models.SystemLog, 0, pageSize)
if err := db.Order("timestamp DESC").Offset((page-1)*pageSize).Limit(pageSize).Find(&items).Error; err != nil {
return nil, err
}
return &QuerySystemLogsResult{
Items: items,
Total: total,
Page: page,
PageSize: pageSize,
}, nil
}