package controller import ( "net/http" "strconv" "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/i18n" "github.com/QuantumNous/new-api/model" "github.com/QuantumNous/new-api/service" "github.com/QuantumNous/new-api/setting/operation_setting" "github.com/gin-gonic/gin" ) // logFilterFromQuery reads the log search shared by the log list and the log // export. Username and channel are read only for administrators. func logFilterFromQuery(c *gin.Context, admin bool) model.LogFilter { logType, _ := strconv.Atoi(c.Query("type")) startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64) endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64) filter := model.LogFilter{ Type: logType, StartTimestamp: startTimestamp, EndTimestamp: endTimestamp, ModelName: c.Query("model_name"), TokenName: c.Query("token_name"), Group: c.Query("group"), RequestId: c.Query("request_id"), UpstreamRequestId: c.Query("upstream_request_id"), } if admin { filter.Username = c.Query("username") filter.Channel, _ = strconv.Atoi(c.Query("channel")) } return filter } // limitLogTimeRange returns the start of a log or dashboard search kept within // the longest log time range. A missing start reaches back the whole range, and // time after now does not count toward it. func limitLogTimeRange(startTimestamp int64, endTimestamp int64) (int64, error) { maxRange := operation_setting.GetLogSetting().MaxRangeSeconds() end := common.GetTimestamp() if endTimestamp != 0 { end = min(end, endTimestamp) } if startTimestamp == 0 { return end - maxRange, nil } if end-startTimestamp > maxRange { return 0, common.NewMessage("The time range cannot exceed {{days}} days", map[string]any{"days": maxRange / (24 * 60 * 60)}) } return startTimestamp, nil } func GetAllLogs(c *gin.Context) { pageInfo := common.GetPageQuery(c) logs, total, err := model.GetAllLogs(logFilterFromQuery(c, true), pageInfo.GetStartIdx(), pageInfo.GetPageSize()) if err != nil { common.ApiError(c, err) return } if c.GetInt("role") < common.RoleRootUser { model.FormatAdminLogs(logs) } else { model.FormatRootLogs(logs) } pageInfo.SetTotal(int(total)) pageInfo.SetItems(logs) common.ApiSuccess(c, pageInfo) return } func GetUserLogs(c *gin.Context) { pageInfo := common.GetPageQuery(c) filter := logFilterFromQuery(c, false) if c.GetInt("role") < common.RoleAdminUser { startTimestamp, err := limitLogTimeRange(filter.StartTimestamp, filter.EndTimestamp) if err != nil { common.ApiError(c, err) return } filter.StartTimestamp = startTimestamp } logs, total, err := model.GetUserLogs(c.GetInt("id"), filter, pageInfo.GetStartIdx(), pageInfo.GetPageSize()) if err != nil { common.ApiError(c, err) return } pageInfo.SetTotal(int(total)) pageInfo.SetItems(logs) common.ApiSuccess(c, pageInfo) return } // ExportAllLogs streams every user's logs matching the search. func ExportAllLogs(c *gin.Context) { exportLogs(c, true) } // ExportUserLogs streams the caller's own logs matching the search. func ExportUserLogs(c *gin.Context) { if c.GetInt("role") < common.RoleAdminUser && !operation_setting.GetLogSetting().UserExportEnabled { common.ApiErrorT(c, "Log export is disabled") return } exportLogs(c, false) } // exportLogs writes one JSON log per line, newest first, and ends with an API // response object: success with the log count, or the failure. Each user runs // one export at a time, and every export stays within the longest log time // range. func exportLogs(c *gin.Context, all bool) { filter := logFilterFromQuery(c, all) startTimestamp, err := limitLogTimeRange(filter.StartTimestamp, filter.EndTimestamp) if err != nil { common.ApiError(c, err) return } filter.StartTimestamp = startTimestamp userId := c.GetInt("id") release, ok, err := service.AcquireLogExportLock(userId) if err != nil { common.SysError(common.LogText("failed to lock log export: %s", err.Error())) common.ApiErrorT(c, "Failed to export logs") return } if !ok { common.ApiErrorT(c, "Another log export is running. Try again after it finishes.") return } defer release() c.Header("Content-Type", "application/x-ndjson; charset=utf-8") c.Header("Cache-Control", "no-store") c.Header("X-Accel-Buffering", "no") c.Status(http.StatusOK) count := 0 writeLog := func(log *model.Log) error { line, err := common.Marshal(log) if err != nil { return err } if _, err := c.Writer.Write(append(line, '\n')); err != nil { return err } count++ return nil } ctx := c.Request.Context() if all { err = model.StreamAllLogs(ctx, filter, c.GetInt("role"), writeLog) } else { err = model.StreamUserLogs(ctx, userId, filter, writeLog) } if ctx.Err() != nil { return } // The status is already sent, so the result object becomes the last line. if err != nil { common.SysError(common.LogText("failed to export logs: %s", err.Error())) common.ApiErrorT(c, "Failed to export logs") return } common.ApiSuccess(c, gin.H{"count": count}) } // Deprecated: SearchAllLogs 已废弃,前端未使用该接口。 func SearchAllLogs(c *gin.Context) { common.ApiErrorT(c, "This API is deprecated") } // Deprecated: SearchUserLogs 已废弃,前端未使用该接口。 func SearchUserLogs(c *gin.Context) { common.ApiErrorT(c, "This API is deprecated") } func GetLogByKey(c *gin.Context) { tokenId := c.GetInt("token_id") if tokenId == 0 { common.ApiErrorI18n(c, i18n.MsgTokenInvalid) return } logs, err := model.GetLogByTokenId(tokenId) if err != nil { c.JSON(200, gin.H{ "success": false, "message": err.Error(), }) return } c.JSON(200, gin.H{ "success": true, "message": "", "data": logs, }) } func GetLogsStat(c *gin.Context) { logType, _ := strconv.Atoi(c.Query("type")) startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64) endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64) tokenName := c.Query("token_name") username := c.Query("username") modelName := c.Query("model_name") channel, _ := strconv.Atoi(c.Query("channel")) group := c.Query("group") stat, err := model.SumUsedQuota(logType, startTimestamp, endTimestamp, modelName, username, tokenName, channel, group) if err != nil { common.ApiError(c, err) return } //tokenNum := model.SumUsedToken(logType, startTimestamp, endTimestamp, modelName, username, "") c.JSON(http.StatusOK, gin.H{ "success": true, "message": "", "data": gin.H{ "quota": stat.Quota, "rpm": stat.Rpm, "tpm": stat.Tpm, }, }) return } func GetLogsSelfStat(c *gin.Context) { username := c.GetString("username") logType, _ := strconv.Atoi(c.Query("type")) startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64) endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64) if c.GetInt("role") < common.RoleAdminUser { var err error if startTimestamp, err = limitLogTimeRange(startTimestamp, endTimestamp); err != nil { common.ApiError(c, err) return } } tokenName := c.Query("token_name") modelName := c.Query("model_name") channel, _ := strconv.Atoi(c.Query("channel")) group := c.Query("group") quotaNum, err := model.SumUsedQuota(logType, startTimestamp, endTimestamp, modelName, username, tokenName, channel, group) if err != nil { common.ApiError(c, err) return } //tokenNum := model.SumUsedToken(logType, startTimestamp, endTimestamp, modelName, username, tokenName) c.JSON(200, gin.H{ "success": true, "message": "", "data": gin.H{ "quota": quotaNum.Quota, "rpm": quotaNum.Rpm, "tpm": quotaNum.Tpm, //"token": tokenNum, }, }) return }