ops_system_log_handler.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. package admin
  2. import (
  3. "net/http"
  4. "strconv"
  5. "strings"
  6. "time"
  7. "github.com/Wei-Shaw/sub2api/internal/pkg/response"
  8. "github.com/Wei-Shaw/sub2api/internal/server/middleware"
  9. "github.com/Wei-Shaw/sub2api/internal/service"
  10. "github.com/gin-gonic/gin"
  11. )
  12. type opsSystemLogCleanupRequest struct {
  13. StartTime string `json:"start_time"`
  14. EndTime string `json:"end_time"`
  15. Host string `json:"host"`
  16. Level string `json:"level"`
  17. Component string `json:"component"`
  18. RequestID string `json:"request_id"`
  19. ClientRequestID string `json:"client_request_id"`
  20. UserID *int64 `json:"user_id"`
  21. APIKeyID *int64 `json:"api_key_id"`
  22. AccountID *int64 `json:"account_id"`
  23. Platform string `json:"platform"`
  24. Model string `json:"model"`
  25. Query string `json:"q"`
  26. }
  27. // ListSystemLogs returns indexed system logs.
  28. // GET /api/v1/admin/ops/system-logs
  29. func (h *OpsHandler) ListSystemLogs(c *gin.Context) {
  30. if h.opsService == nil {
  31. response.Error(c, http.StatusServiceUnavailable, "Ops service not available")
  32. return
  33. }
  34. if err := h.opsService.RequireMonitoringEnabled(c.Request.Context()); err != nil {
  35. response.ErrorFrom(c, err)
  36. return
  37. }
  38. page, pageSize := response.ParsePagination(c)
  39. if pageSize > 200 {
  40. pageSize = 200
  41. }
  42. start, end, err := parseOpsTimeRange(c, "1h")
  43. if err != nil {
  44. response.BadRequest(c, err.Error())
  45. return
  46. }
  47. filter := &service.OpsSystemLogFilter{
  48. Page: page,
  49. PageSize: pageSize,
  50. StartTime: &start,
  51. EndTime: &end,
  52. Host: strings.TrimSpace(c.Query("host")),
  53. Level: strings.TrimSpace(c.Query("level")),
  54. Component: strings.TrimSpace(c.Query("component")),
  55. RequestID: strings.TrimSpace(c.Query("request_id")),
  56. ClientRequestID: strings.TrimSpace(c.Query("client_request_id")),
  57. Platform: strings.TrimSpace(c.Query("platform")),
  58. Model: strings.TrimSpace(c.Query("model")),
  59. Query: strings.TrimSpace(c.Query("q")),
  60. }
  61. if v := strings.TrimSpace(c.Query("user_id")); v != "" {
  62. id, parseErr := strconv.ParseInt(v, 10, 64)
  63. if parseErr != nil || id <= 0 {
  64. response.BadRequest(c, "Invalid user_id")
  65. return
  66. }
  67. filter.UserID = &id
  68. }
  69. if v := strings.TrimSpace(c.Query("api_key_id")); v != "" {
  70. id, parseErr := strconv.ParseInt(v, 10, 64)
  71. if parseErr != nil || id <= 0 {
  72. response.BadRequest(c, "Invalid api_key_id")
  73. return
  74. }
  75. filter.APIKeyID = &id
  76. }
  77. if v := strings.TrimSpace(c.Query("account_id")); v != "" {
  78. id, parseErr := strconv.ParseInt(v, 10, 64)
  79. if parseErr != nil || id <= 0 {
  80. response.BadRequest(c, "Invalid account_id")
  81. return
  82. }
  83. filter.AccountID = &id
  84. }
  85. result, err := h.opsService.ListSystemLogs(c.Request.Context(), filter)
  86. if err != nil {
  87. response.ErrorFrom(c, err)
  88. return
  89. }
  90. response.Paginated(c, result.Logs, int64(result.Total), result.Page, result.PageSize)
  91. }
  92. // CleanupSystemLogs deletes indexed system logs by filter.
  93. // POST /api/v1/admin/ops/system-logs/cleanup
  94. func (h *OpsHandler) CleanupSystemLogs(c *gin.Context) {
  95. if h.opsService == nil {
  96. response.Error(c, http.StatusServiceUnavailable, "Ops service not available")
  97. return
  98. }
  99. if err := h.opsService.RequireMonitoringEnabled(c.Request.Context()); err != nil {
  100. response.ErrorFrom(c, err)
  101. return
  102. }
  103. subject, ok := middleware.GetAuthSubjectFromContext(c)
  104. if !ok || subject.UserID <= 0 {
  105. response.Error(c, http.StatusUnauthorized, "Unauthorized")
  106. return
  107. }
  108. var req opsSystemLogCleanupRequest
  109. if err := c.ShouldBindJSON(&req); err != nil {
  110. response.BadRequest(c, "Invalid request body")
  111. return
  112. }
  113. parseTS := func(raw string) (*time.Time, error) {
  114. raw = strings.TrimSpace(raw)
  115. if raw == "" {
  116. return nil, nil
  117. }
  118. if t, err := time.Parse(time.RFC3339Nano, raw); err == nil {
  119. return &t, nil
  120. }
  121. t, err := time.Parse(time.RFC3339, raw)
  122. if err != nil {
  123. return nil, err
  124. }
  125. return &t, nil
  126. }
  127. start, err := parseTS(req.StartTime)
  128. if err != nil {
  129. response.BadRequest(c, "Invalid start_time")
  130. return
  131. }
  132. end, err := parseTS(req.EndTime)
  133. if err != nil {
  134. response.BadRequest(c, "Invalid end_time")
  135. return
  136. }
  137. if req.APIKeyID != nil && *req.APIKeyID <= 0 {
  138. response.BadRequest(c, "Invalid api_key_id")
  139. return
  140. }
  141. filter := &service.OpsSystemLogCleanupFilter{
  142. StartTime: start,
  143. EndTime: end,
  144. Host: strings.TrimSpace(req.Host),
  145. Level: strings.TrimSpace(req.Level),
  146. Component: strings.TrimSpace(req.Component),
  147. RequestID: strings.TrimSpace(req.RequestID),
  148. ClientRequestID: strings.TrimSpace(req.ClientRequestID),
  149. UserID: req.UserID,
  150. APIKeyID: req.APIKeyID,
  151. AccountID: req.AccountID,
  152. Platform: strings.TrimSpace(req.Platform),
  153. Model: strings.TrimSpace(req.Model),
  154. Query: strings.TrimSpace(req.Query),
  155. }
  156. deleted, err := h.opsService.CleanupSystemLogs(c.Request.Context(), filter, subject.UserID)
  157. if err != nil {
  158. response.ErrorFrom(c, err)
  159. return
  160. }
  161. response.Success(c, gin.H{"deleted": deleted})
  162. }
  163. // GetSystemLogIngestionHealth returns sink health metrics.
  164. // GET /api/v1/admin/ops/system-logs/health
  165. func (h *OpsHandler) GetSystemLogIngestionHealth(c *gin.Context) {
  166. if h.opsService == nil {
  167. response.Error(c, http.StatusServiceUnavailable, "Ops service not available")
  168. return
  169. }
  170. if err := h.opsService.RequireMonitoringEnabled(c.Request.Context()); err != nil {
  171. response.ErrorFrom(c, err)
  172. return
  173. }
  174. response.Success(c, h.opsService.GetSystemLogSinkHealth())
  175. }