system_log_service.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. package service
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "strings"
  6. "sync/atomic"
  7. "time"
  8. "github.com/2930134478/AI-CS/backend/models"
  9. "github.com/2930134478/AI-CS/backend/repository"
  10. )
  11. type CreateSystemLogInput struct {
  12. Level string
  13. Category string
  14. Event string
  15. Source string
  16. TraceID string
  17. ConversationID *uint
  18. UserID *uint
  19. VisitorID *uint
  20. Message string
  21. Meta map[string]interface{}
  22. Timestamp *time.Time
  23. }
  24. type QuerySystemLogsInput struct {
  25. From string
  26. To string
  27. Level string
  28. Category string
  29. Event string
  30. Source string
  31. ConversationID *uint
  32. Keyword string
  33. Page int
  34. PageSize int
  35. }
  36. type QuerySystemLogsResult struct {
  37. Items []models.SystemLog `json:"items"`
  38. Total int64 `json:"total"`
  39. Page int `json:"page"`
  40. PageSize int `json:"page_size"`
  41. }
  42. // SystemLogService 结构化日志服务(查询 + 写入)。
  43. type SystemLogService struct {
  44. repo *repository.SystemLogRepository
  45. minPersistLevel atomic.Int32 // 见 SYSTEM_LOG_MIN_LEVEL / 数据库覆盖;logRankNone 表示关闭全部落库
  46. }
  47. func NewSystemLogService(repo *repository.SystemLogRepository, minPersistLevel int) *SystemLogService {
  48. s := &SystemLogService{repo: repo}
  49. s.SetMinPersistLevelRank(minPersistLevel)
  50. return s
  51. }
  52. // SetMinPersistLevelRank 运行时调整最低落库级别(线程安全)。
  53. func (s *SystemLogService) SetMinPersistLevelRank(rank int) {
  54. if s == nil {
  55. return
  56. }
  57. s.minPersistLevel.Store(int32(rank))
  58. }
  59. // MinPersistLevelRank 当前生效的最低落库级别数值。
  60. func (s *SystemLogService) MinPersistLevelRank() int {
  61. if s == nil {
  62. return logRankInfo
  63. }
  64. return int(s.minPersistLevel.Load())
  65. }
  66. func (s *SystemLogService) shouldPersistLevel(level string) bool {
  67. if s == nil {
  68. return false
  69. }
  70. cur := int(s.minPersistLevel.Load())
  71. if cur == logRankNone {
  72. return false
  73. }
  74. return logLevelRank(level) >= cur
  75. }
  76. // ShouldPersistLevel 供中间件等在组装大 payload 前判断,避免无谓开销。
  77. func (s *SystemLogService) ShouldPersistLevel(level string) bool {
  78. return s.shouldPersistLevel(strings.ToLower(strings.TrimSpace(level)))
  79. }
  80. func (s *SystemLogService) Create(input CreateSystemLogInput) error {
  81. level := strings.ToLower(strings.TrimSpace(input.Level))
  82. if level == "" {
  83. level = "info"
  84. }
  85. if !s.shouldPersistLevel(level) {
  86. return nil
  87. }
  88. category := strings.ToLower(strings.TrimSpace(input.Category))
  89. if category == "" {
  90. category = "system"
  91. }
  92. source := strings.ToLower(strings.TrimSpace(input.Source))
  93. if source == "" {
  94. source = "backend"
  95. }
  96. event := strings.TrimSpace(input.Event)
  97. if event == "" {
  98. event = "general"
  99. }
  100. message := strings.TrimSpace(input.Message)
  101. if message == "" {
  102. return fmt.Errorf("message 不能为空")
  103. }
  104. metaJSON := ""
  105. if input.Meta != nil {
  106. if b, err := json.Marshal(input.Meta); err == nil {
  107. metaJSON = string(b)
  108. }
  109. }
  110. ts := time.Now()
  111. if input.Timestamp != nil {
  112. ts = *input.Timestamp
  113. }
  114. item := &models.SystemLog{
  115. Timestamp: ts,
  116. Level: level,
  117. Category: category,
  118. Event: event,
  119. Source: source,
  120. TraceID: strings.TrimSpace(input.TraceID),
  121. ConversationID: input.ConversationID,
  122. UserID: input.UserID,
  123. VisitorID: input.VisitorID,
  124. Message: message,
  125. MetaJSON: metaJSON,
  126. }
  127. return s.repo.Create(item)
  128. }
  129. func (s *SystemLogService) Query(input QuerySystemLogsInput) (*QuerySystemLogsResult, error) {
  130. page := input.Page
  131. if page <= 0 {
  132. page = 1
  133. }
  134. pageSize := input.PageSize
  135. if pageSize <= 0 {
  136. pageSize = 50
  137. }
  138. if pageSize > 200 {
  139. pageSize = 200
  140. }
  141. db := s.repo.DB().Model(&models.SystemLog{})
  142. if input.From != "" {
  143. if t, err := time.Parse("2006-01-02", input.From); err == nil {
  144. db = db.Where("timestamp >= ?", t)
  145. }
  146. }
  147. if input.To != "" {
  148. if t, err := time.Parse("2006-01-02", input.To); err == nil {
  149. db = db.Where("timestamp < ?", t.AddDate(0, 0, 1))
  150. }
  151. }
  152. if v := strings.TrimSpace(input.Level); v != "" {
  153. db = db.Where("level = ?", strings.ToLower(v))
  154. }
  155. if v := strings.TrimSpace(input.Category); v != "" {
  156. db = db.Where("category = ?", strings.ToLower(v))
  157. }
  158. if v := strings.TrimSpace(input.Event); v != "" {
  159. db = db.Where("event = ?", v)
  160. }
  161. if v := strings.TrimSpace(input.Source); v != "" {
  162. db = db.Where("source = ?", strings.ToLower(v))
  163. }
  164. if input.ConversationID != nil {
  165. db = db.Where("conversation_id = ?", *input.ConversationID)
  166. }
  167. if v := strings.TrimSpace(input.Keyword); v != "" {
  168. like := "%" + v + "%"
  169. db = db.Where("(message LIKE ? OR meta_json LIKE ? OR trace_id LIKE ?)", like, like, like)
  170. }
  171. var total int64
  172. if err := db.Count(&total).Error; err != nil {
  173. return nil, err
  174. }
  175. items := make([]models.SystemLog, 0, pageSize)
  176. if err := db.Order("timestamp DESC").Offset((page-1)*pageSize).Limit(pageSize).Find(&items).Error; err != nil {
  177. return nil, err
  178. }
  179. return &QuerySystemLogsResult{
  180. Items: items,
  181. Total: total,
  182. Page: page,
  183. PageSize: pageSize,
  184. }, nil
  185. }