feat(log): wire log db, metrics, and body toggle

This commit is contained in:
zenfun
2025-12-21 16:18:22 +08:00
parent 4c1e03f83d
commit c2c65e774b
9 changed files with 305 additions and 40 deletions

View File

@@ -8,6 +8,7 @@ import (
"github.com/ez-api/ez-api/internal/model"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type LogView struct {
@@ -52,6 +53,38 @@ func toLogView(r model.LogRecord) LogView {
}
}
type MasterLogView struct {
ID uint `json:"id"`
CreatedAt int64 `json:"created_at"`
Group string `json:"group"`
KeyID uint `json:"key_id"`
ModelName string `json:"model"`
StatusCode int `json:"status_code"`
LatencyMs int64 `json:"latency_ms"`
TokensIn int64 `json:"tokens_in"`
TokensOut int64 `json:"tokens_out"`
ErrorMessage string `json:"error_message"`
RequestSize int64 `json:"request_size"`
ResponseSize int64 `json:"response_size"`
}
func toMasterLogView(r model.LogRecord) MasterLogView {
return MasterLogView{
ID: r.ID,
CreatedAt: r.CreatedAt.UTC().Unix(),
Group: r.Group,
KeyID: r.KeyID,
ModelName: r.ModelName,
StatusCode: r.StatusCode,
LatencyMs: r.LatencyMs,
TokensIn: r.TokensIn,
TokensOut: r.TokensOut,
ErrorMessage: r.ErrorMessage,
RequestSize: r.RequestSize,
ResponseSize: r.ResponseSize,
}
}
type ListLogsResponse struct {
Total int64 `json:"total"`
Limit int `json:"limit"`
@@ -59,6 +92,13 @@ type ListLogsResponse struct {
Items []LogView `json:"items"`
}
type ListMasterLogsResponse struct {
Total int64 `json:"total"`
Limit int `json:"limit"`
Offset int `json:"offset"`
Items []MasterLogView `json:"items"`
}
func parseLimitOffset(c *gin.Context) (limit, offset int) {
limit = 50
offset = 0
@@ -90,6 +130,26 @@ func parseUnixSeconds(raw string) (time.Time, bool) {
return time.Unix(sec, 0).UTC(), true
}
func (h *MasterHandler) masterLogBase(masterID uint) (*gorm.DB, error) {
logDB := h.logDBConn()
if logDB == h.db {
return logDB.Model(&model.LogRecord{}).
Joins("JOIN keys ON keys.id = log_records.key_id").
Where("keys.master_id = ?", masterID), nil
}
var keyIDs []uint
if err := h.db.Model(&model.Key{}).
Where("master_id = ?", masterID).
Pluck("id", &keyIDs).Error; err != nil {
return nil, err
}
if len(keyIDs) == 0 {
return logDB.Model(&model.LogRecord{}).Where("1 = 0"), nil
}
return logDB.Model(&model.LogRecord{}).
Where("log_records.key_id IN ?", keyIDs), nil
}
// ListLogs godoc
// @Summary List logs (admin)
// @Description List request logs with basic filtering/pagination
@@ -104,13 +164,13 @@ func parseUnixSeconds(raw string) (time.Time, bool) {
// @Param group query string false "route group"
// @Param model query string false "model"
// @Param status_code query int false "status code"
// @Success 200 {object} ListLogsResponse
// @Success 200 {object} ListMasterLogsResponse
// @Failure 500 {object} gin.H
// @Router /admin/logs [get]
func (h *Handler) ListLogs(c *gin.Context) {
limit, offset := parseLimitOffset(c)
q := h.db.Model(&model.LogRecord{})
q := h.logDBConn().Model(&model.LogRecord{})
if t, ok := parseUnixSeconds(c.Query("since")); ok {
q = q.Where("created_at >= ?", t)
@@ -146,11 +206,11 @@ func (h *Handler) ListLogs(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list logs", "details": err.Error()})
return
}
out := make([]LogView, 0, len(rows))
out := make([]MasterLogView, 0, len(rows))
for _, r := range rows {
out = append(out, toLogView(r))
out = append(out, toMasterLogView(r))
}
c.JSON(http.StatusOK, ListLogsResponse{Total: total, Limit: limit, Offset: offset, Items: out})
c.JSON(http.StatusOK, ListMasterLogsResponse{Total: total, Limit: limit, Offset: offset, Items: out})
}
type LogStatsResponse struct {
@@ -201,7 +261,7 @@ func (h *Handler) DeleteLogs(c *gin.Context) {
return
}
q := h.db.Unscoped().Where("created_at < ?", ts.UTC())
q := h.logDBConn().Unscoped().Where("created_at < ?", ts.UTC())
if req.KeyID > 0 {
q = q.Where("key_id = ?", req.KeyID)
}
@@ -229,7 +289,7 @@ func (h *Handler) DeleteLogs(c *gin.Context) {
// @Failure 500 {object} gin.H
// @Router /admin/logs/stats [get]
func (h *Handler) LogStats(c *gin.Context) {
q := h.db.Model(&model.LogRecord{})
q := h.logDBConn().Model(&model.LogRecord{})
if t, ok := parseUnixSeconds(c.Query("since")); ok {
q = q.Where("created_at >= ?", t)
}
@@ -289,7 +349,7 @@ func (h *Handler) LogStats(c *gin.Context) {
// @Param until query int false "unix seconds"
// @Param model query string false "model"
// @Param status_code query int false "status code"
// @Success 200 {object} ListLogsResponse
// @Success 200 {object} ListMasterLogsResponse
// @Failure 401 {object} gin.H
// @Failure 500 {object} gin.H
// @Router /v1/logs [get]
@@ -302,9 +362,11 @@ func (h *MasterHandler) ListSelfLogs(c *gin.Context) {
m := master.(*model.Master)
limit, offset := parseLimitOffset(c)
q := h.db.Model(&model.LogRecord{}).
Joins("JOIN keys ON keys.id = log_records.key_id").
Where("keys.master_id = ?", m.ID)
q, err := h.masterLogBase(m.ID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to build log query", "details": err.Error()})
return
}
if t, ok := parseUnixSeconds(c.Query("since")); ok {
q = q.Where("log_records.created_at >= ?", t)
@@ -331,11 +393,11 @@ func (h *MasterHandler) ListSelfLogs(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list logs", "details": err.Error()})
return
}
out := make([]LogView, 0, len(rows))
out := make([]MasterLogView, 0, len(rows))
for _, r := range rows {
out = append(out, toLogView(r))
out = append(out, toMasterLogView(r))
}
c.JSON(http.StatusOK, ListLogsResponse{Total: total, Limit: limit, Offset: offset, Items: out})
c.JSON(http.StatusOK, ListMasterLogsResponse{Total: total, Limit: limit, Offset: offset, Items: out})
}
// GetSelfLogStats godoc
@@ -358,9 +420,11 @@ func (h *MasterHandler) GetSelfLogStats(c *gin.Context) {
}
m := master.(*model.Master)
q := h.db.Model(&model.LogRecord{}).
Joins("JOIN keys ON keys.id = log_records.key_id").
Where("keys.master_id = ?", m.ID)
q, err := h.masterLogBase(m.ID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to build log query", "details": err.Error()})
return
}
if t, ok := parseUnixSeconds(c.Query("since")); ok {
q = q.Where("log_records.created_at >= ?", t)