2023-06-09 16:59:00 +08:00
|
|
|
|
package model
|
|
|
|
|
|
|
2023-06-10 16:04:04 +08:00
|
|
|
|
import (
|
2025-04-10 22:52:17 +08:00
|
|
|
|
"context"
|
2026-02-06 21:26:26 +08:00
|
|
|
|
"errors"
|
2023-09-17 15:39:46 +08:00
|
|
|
|
"fmt"
|
2024-08-01 16:13:08 +08:00
|
|
|
|
"time"
|
2024-08-11 11:21:34 +08:00
|
|
|
|
|
2025-10-11 15:30:09 +08:00
|
|
|
|
"github.com/QuantumNous/new-api/common"
|
|
|
|
|
|
"github.com/QuantumNous/new-api/logger"
|
|
|
|
|
|
"github.com/QuantumNous/new-api/types"
|
|
|
|
|
|
|
2025-03-02 17:57:52 +08:00
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
|
|
2024-08-11 11:21:34 +08:00
|
|
|
|
"github.com/bytedance/gopkg/util/gopool"
|
|
|
|
|
|
"gorm.io/gorm"
|
2023-06-10 16:04:04 +08:00
|
|
|
|
)
|
2023-06-09 16:59:00 +08:00
|
|
|
|
|
|
|
|
|
|
type Log struct {
|
2026-02-07 23:20:43 +08:00
|
|
|
|
Id int `json:"id" gorm:"index:idx_created_at_id,priority:1;index:idx_user_id_id,priority:2"`
|
|
|
|
|
|
UserId int `json:"user_id" gorm:"index;index:idx_user_id_id,priority:1"`
|
2023-10-02 12:11:02 +08:00
|
|
|
|
CreatedAt int64 `json:"created_at" gorm:"bigint;index:idx_created_at_id,priority:2;index:idx_created_at_type"`
|
|
|
|
|
|
Type int `json:"type" gorm:"index:idx_created_at_type"`
|
2023-06-24 15:28:11 +08:00
|
|
|
|
Content string `json:"content"`
|
2025-03-02 17:57:52 +08:00
|
|
|
|
Username string `json:"username" gorm:"index;index:index_username_model_name,priority:2;default:''"`
|
2023-06-24 15:28:11 +08:00
|
|
|
|
TokenName string `json:"token_name" gorm:"index;default:''"`
|
2023-10-02 12:11:02 +08:00
|
|
|
|
ModelName string `json:"model_name" gorm:"index;index:index_username_model_name,priority:1;default:''"`
|
2023-06-24 15:28:11 +08:00
|
|
|
|
Quota int `json:"quota" gorm:"default:0"`
|
|
|
|
|
|
PromptTokens int `json:"prompt_tokens" gorm:"default:0"`
|
|
|
|
|
|
CompletionTokens int `json:"completion_tokens" gorm:"default:0"`
|
2024-01-22 17:07:18 +08:00
|
|
|
|
UseTime int `json:"use_time" gorm:"default:0"`
|
2025-07-18 13:29:15 +08:00
|
|
|
|
IsStream bool `json:"is_stream"`
|
2023-09-29 18:13:57 +08:00
|
|
|
|
ChannelId int `json:"channel" gorm:"index"`
|
2025-01-09 15:20:12 +08:00
|
|
|
|
ChannelName string `json:"channel_name" gorm:"->"`
|
2023-08-14 22:16:32 +08:00
|
|
|
|
TokenId int `json:"token_id" gorm:"default:0;index"`
|
2024-12-24 14:48:11 +08:00
|
|
|
|
Group string `json:"group" gorm:"index"`
|
2025-06-13 01:34:01 +08:00
|
|
|
|
Ip string `json:"ip" gorm:"index;default:''"`
|
2026-02-04 02:12:18 +08:00
|
|
|
|
RequestId string `json:"request_id,omitempty" gorm:"type:varchar(64);index:idx_logs_request_id;default:''"`
|
2024-05-12 15:35:57 +08:00
|
|
|
|
Other string `json:"other"`
|
2023-06-09 16:59:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-15 19:54:13 +08:00
|
|
|
|
// don't use iota, avoid change log type value
|
2023-06-10 16:04:04 +08:00
|
|
|
|
const (
|
2025-10-15 19:54:13 +08:00
|
|
|
|
LogTypeUnknown = 0
|
|
|
|
|
|
LogTypeTopup = 1
|
|
|
|
|
|
LogTypeConsume = 2
|
|
|
|
|
|
LogTypeManage = 3
|
|
|
|
|
|
LogTypeSystem = 4
|
|
|
|
|
|
LogTypeError = 5
|
|
|
|
|
|
LogTypeRefund = 6
|
2023-06-10 16:04:04 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-02-07 22:51:26 +08:00
|
|
|
|
func formatUserLogs(logs []*Log, startIdx int) {
|
2024-12-28 16:40:29 +08:00
|
|
|
|
for i := range logs {
|
2025-01-27 13:31:24 +08:00
|
|
|
|
logs[i].ChannelName = ""
|
2024-12-28 16:40:29 +08:00
|
|
|
|
var otherMap map[string]interface{}
|
2025-07-06 12:37:56 +08:00
|
|
|
|
otherMap, _ = common.StrToMap(logs[i].Other)
|
2024-12-28 16:40:29 +08:00
|
|
|
|
if otherMap != nil {
|
2026-01-20 23:43:29 +08:00
|
|
|
|
// Remove admin-only debug fields.
|
2024-12-28 16:40:29 +08:00
|
|
|
|
delete(otherMap, "admin_info")
|
2026-03-31 16:50:24 +08:00
|
|
|
|
// delete(otherMap, "reject_reason")
|
|
|
|
|
|
delete(otherMap, "stream_status")
|
2024-12-28 16:40:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
logs[i].Other = common.MapToJsonStr(otherMap)
|
2026-02-07 22:51:26 +08:00
|
|
|
|
logs[i].Id = startIdx + i + 1
|
2024-12-28 16:40:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 21:26:26 +08:00
|
|
|
|
func GetLogByTokenId(tokenId int) (logs []*Log, err error) {
|
|
|
|
|
|
err = LOG_DB.Model(&Log{}).Where("token_id = ?", tokenId).Order("id desc").Limit(common.MaxRecentItems).Find(&logs).Error
|
2026-02-07 22:51:26 +08:00
|
|
|
|
formatUserLogs(logs, 0)
|
2023-08-14 22:16:32 +08:00
|
|
|
|
return logs, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-06-09 16:59:00 +08:00
|
|
|
|
func RecordLog(userId int, logType int, content string) {
|
2023-06-15 16:32:16 +08:00
|
|
|
|
if logType == LogTypeConsume && !common.LogConsumeEnabled {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2024-12-29 16:50:26 +08:00
|
|
|
|
username, _ := GetUsernameById(userId, false)
|
2023-06-09 16:59:00 +08:00
|
|
|
|
log := &Log{
|
|
|
|
|
|
UserId: userId,
|
2024-01-11 18:39:21 +08:00
|
|
|
|
Username: username,
|
2023-06-09 16:59:00 +08:00
|
|
|
|
CreatedAt: common.GetTimestamp(),
|
|
|
|
|
|
Type: logType,
|
|
|
|
|
|
Content: content,
|
|
|
|
|
|
}
|
2024-08-13 10:29:55 +08:00
|
|
|
|
err := LOG_DB.Create(log).Error
|
2023-06-09 16:59:00 +08:00
|
|
|
|
if err != nil {
|
2025-08-14 21:10:04 +08:00
|
|
|
|
common.SysLog("failed to record log: " + err.Error())
|
2023-06-09 16:59:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-18 00:16:52 +08:00
|
|
|
|
// RecordLogWithAdminInfo 记录操作日志,并将管理员相关信息存入 Other.admin_info,
|
|
|
|
|
|
func RecordLogWithAdminInfo(userId int, logType int, content string, adminInfo map[string]interface{}) {
|
|
|
|
|
|
if logType == LogTypeConsume && !common.LogConsumeEnabled {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
username, _ := GetUsernameById(userId, false)
|
|
|
|
|
|
log := &Log{
|
|
|
|
|
|
UserId: userId,
|
|
|
|
|
|
Username: username,
|
|
|
|
|
|
CreatedAt: common.GetTimestamp(),
|
|
|
|
|
|
Type: logType,
|
|
|
|
|
|
Content: content,
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(adminInfo) > 0 {
|
|
|
|
|
|
other := map[string]interface{}{
|
|
|
|
|
|
"admin_info": adminInfo,
|
|
|
|
|
|
}
|
|
|
|
|
|
log.Other = common.MapToJsonStr(other)
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := LOG_DB.Create(log).Error; err != nil {
|
|
|
|
|
|
common.SysLog("failed to record log: " + err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-17 23:51:30 +08:00
|
|
|
|
func RecordTopupLog(userId int, content string, callerIp string, paymentMethod string, callbackPaymentMethod string) {
|
|
|
|
|
|
username, _ := GetUsernameById(userId, false)
|
|
|
|
|
|
adminInfo := map[string]interface{}{
|
|
|
|
|
|
"server_ip": common.GetIp(),
|
2026-04-18 00:51:04 +08:00
|
|
|
|
"node_name": common.NodeName,
|
2026-04-17 23:51:30 +08:00
|
|
|
|
"caller_ip": callerIp,
|
|
|
|
|
|
"payment_method": paymentMethod,
|
|
|
|
|
|
"callback_payment_method": callbackPaymentMethod,
|
|
|
|
|
|
"version": common.Version,
|
|
|
|
|
|
}
|
|
|
|
|
|
other := map[string]interface{}{
|
|
|
|
|
|
"admin_info": adminInfo,
|
|
|
|
|
|
}
|
|
|
|
|
|
log := &Log{
|
|
|
|
|
|
UserId: userId,
|
|
|
|
|
|
Username: username,
|
|
|
|
|
|
CreatedAt: common.GetTimestamp(),
|
|
|
|
|
|
Type: LogTypeTopup,
|
|
|
|
|
|
Content: content,
|
|
|
|
|
|
Ip: callerIp,
|
|
|
|
|
|
Other: common.MapToJsonStr(other),
|
|
|
|
|
|
}
|
|
|
|
|
|
err := LOG_DB.Create(log).Error
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
common.SysLog("failed to record topup log: " + err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-12 00:43:34 +08:00
|
|
|
|
func RecordErrorLog(c *gin.Context, userId int, channelId int, modelName string, tokenName string, content string, tokenId int, useTimeSeconds int,
|
|
|
|
|
|
isStream bool, group string, other map[string]interface{}) {
|
2025-08-14 20:05:06 +08:00
|
|
|
|
logger.LogInfo(c, fmt.Sprintf("record error log: userId=%d, channelId=%d, modelName=%s, tokenName=%s, content=%s", userId, channelId, modelName, tokenName, content))
|
2025-04-12 00:43:34 +08:00
|
|
|
|
username := c.GetString("username")
|
2026-02-04 14:47:42 +08:00
|
|
|
|
requestId := c.GetString(common.RequestIdKey)
|
2025-04-12 00:43:34 +08:00
|
|
|
|
otherStr := common.MapToJsonStr(other)
|
2025-06-13 01:34:01 +08:00
|
|
|
|
// 判断是否需要记录 IP
|
|
|
|
|
|
needRecordIp := false
|
|
|
|
|
|
if settingMap, err := GetUserSetting(userId, false); err == nil {
|
2025-07-07 14:26:37 +08:00
|
|
|
|
if settingMap.RecordIpLog {
|
|
|
|
|
|
needRecordIp = true
|
2025-06-13 01:34:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-04-12 00:43:34 +08:00
|
|
|
|
log := &Log{
|
|
|
|
|
|
UserId: userId,
|
|
|
|
|
|
Username: username,
|
|
|
|
|
|
CreatedAt: common.GetTimestamp(),
|
|
|
|
|
|
Type: LogTypeError,
|
|
|
|
|
|
Content: content,
|
|
|
|
|
|
PromptTokens: 0,
|
|
|
|
|
|
CompletionTokens: 0,
|
|
|
|
|
|
TokenName: tokenName,
|
|
|
|
|
|
ModelName: modelName,
|
|
|
|
|
|
Quota: 0,
|
|
|
|
|
|
ChannelId: channelId,
|
|
|
|
|
|
TokenId: tokenId,
|
|
|
|
|
|
UseTime: useTimeSeconds,
|
|
|
|
|
|
IsStream: isStream,
|
|
|
|
|
|
Group: group,
|
2025-06-14 17:51:05 +08:00
|
|
|
|
Ip: func() string {
|
|
|
|
|
|
if needRecordIp {
|
|
|
|
|
|
return c.ClientIP()
|
|
|
|
|
|
}
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}(),
|
2026-02-04 14:47:42 +08:00
|
|
|
|
RequestId: requestId,
|
2026-02-04 02:12:18 +08:00
|
|
|
|
Other: otherStr,
|
2025-04-12 00:43:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
err := LOG_DB.Create(log).Error
|
|
|
|
|
|
if err != nil {
|
2025-08-14 20:05:06 +08:00
|
|
|
|
logger.LogError(c, "failed to record log: "+err.Error())
|
2025-04-12 00:43:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-07 14:26:37 +08:00
|
|
|
|
type RecordConsumeLogParams struct {
|
|
|
|
|
|
ChannelId int `json:"channel_id"`
|
|
|
|
|
|
PromptTokens int `json:"prompt_tokens"`
|
|
|
|
|
|
CompletionTokens int `json:"completion_tokens"`
|
|
|
|
|
|
ModelName string `json:"model_name"`
|
|
|
|
|
|
TokenName string `json:"token_name"`
|
|
|
|
|
|
Quota int `json:"quota"`
|
|
|
|
|
|
Content string `json:"content"`
|
|
|
|
|
|
TokenId int `json:"token_id"`
|
|
|
|
|
|
UseTimeSeconds int `json:"use_time_seconds"`
|
|
|
|
|
|
IsStream bool `json:"is_stream"`
|
|
|
|
|
|
Group string `json:"group"`
|
|
|
|
|
|
Other map[string]interface{} `json:"other"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func RecordConsumeLog(c *gin.Context, userId int, params RecordConsumeLogParams) {
|
2023-06-24 15:28:11 +08:00
|
|
|
|
if !common.LogConsumeEnabled {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-08-16 19:11:15 +08:00
|
|
|
|
logger.LogInfo(c, fmt.Sprintf("record consume log: userId=%d, params=%s", userId, common.GetJsonString(params)))
|
2025-02-25 20:56:16 +08:00
|
|
|
|
username := c.GetString("username")
|
2026-02-04 14:47:42 +08:00
|
|
|
|
requestId := c.GetString(common.RequestIdKey)
|
2025-07-07 14:26:37 +08:00
|
|
|
|
otherStr := common.MapToJsonStr(params.Other)
|
2025-06-13 01:34:01 +08:00
|
|
|
|
// 判断是否需要记录 IP
|
|
|
|
|
|
needRecordIp := false
|
|
|
|
|
|
if settingMap, err := GetUserSetting(userId, false); err == nil {
|
2025-07-07 14:26:37 +08:00
|
|
|
|
if settingMap.RecordIpLog {
|
|
|
|
|
|
needRecordIp = true
|
2025-06-13 01:34:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2023-06-24 15:28:11 +08:00
|
|
|
|
log := &Log{
|
|
|
|
|
|
UserId: userId,
|
2024-01-07 18:31:14 +08:00
|
|
|
|
Username: username,
|
2023-06-24 15:28:11 +08:00
|
|
|
|
CreatedAt: common.GetTimestamp(),
|
|
|
|
|
|
Type: LogTypeConsume,
|
2025-07-07 14:26:37 +08:00
|
|
|
|
Content: params.Content,
|
|
|
|
|
|
PromptTokens: params.PromptTokens,
|
|
|
|
|
|
CompletionTokens: params.CompletionTokens,
|
|
|
|
|
|
TokenName: params.TokenName,
|
|
|
|
|
|
ModelName: params.ModelName,
|
|
|
|
|
|
Quota: params.Quota,
|
|
|
|
|
|
ChannelId: params.ChannelId,
|
|
|
|
|
|
TokenId: params.TokenId,
|
|
|
|
|
|
UseTime: params.UseTimeSeconds,
|
|
|
|
|
|
IsStream: params.IsStream,
|
|
|
|
|
|
Group: params.Group,
|
2025-06-14 17:51:05 +08:00
|
|
|
|
Ip: func() string {
|
|
|
|
|
|
if needRecordIp {
|
|
|
|
|
|
return c.ClientIP()
|
|
|
|
|
|
}
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}(),
|
2026-02-04 14:47:42 +08:00
|
|
|
|
RequestId: requestId,
|
2026-02-04 02:12:18 +08:00
|
|
|
|
Other: otherStr,
|
2023-06-24 15:28:11 +08:00
|
|
|
|
}
|
2024-08-13 10:29:55 +08:00
|
|
|
|
err := LOG_DB.Create(log).Error
|
2023-06-24 15:28:11 +08:00
|
|
|
|
if err != nil {
|
2025-08-14 20:05:06 +08:00
|
|
|
|
logger.LogError(c, "failed to record log: "+err.Error())
|
2023-06-24 15:28:11 +08:00
|
|
|
|
}
|
2024-01-07 18:31:14 +08:00
|
|
|
|
if common.DataExportEnabled {
|
2024-07-19 01:07:37 +08:00
|
|
|
|
gopool.Go(func() {
|
2025-07-07 14:26:37 +08:00
|
|
|
|
LogQuotaData(userId, username, params.ModelName, params.Quota, common.GetTimestamp(), params.PromptTokens+params.CompletionTokens)
|
2024-01-12 13:49:53 +08:00
|
|
|
|
})
|
2024-01-07 18:31:14 +08:00
|
|
|
|
}
|
2023-06-24 15:28:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-21 22:48:30 +08:00
|
|
|
|
type RecordTaskBillingLogParams struct {
|
|
|
|
|
|
UserId int
|
|
|
|
|
|
LogType int
|
|
|
|
|
|
Content string
|
|
|
|
|
|
ChannelId int
|
|
|
|
|
|
ModelName string
|
|
|
|
|
|
Quota int
|
|
|
|
|
|
TokenId int
|
|
|
|
|
|
Group string
|
|
|
|
|
|
Other map[string]interface{}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func RecordTaskBillingLog(params RecordTaskBillingLogParams) {
|
|
|
|
|
|
if params.LogType == LogTypeConsume && !common.LogConsumeEnabled {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
username, _ := GetUsernameById(params.UserId, false)
|
|
|
|
|
|
tokenName := ""
|
|
|
|
|
|
if params.TokenId > 0 {
|
|
|
|
|
|
if token, err := GetTokenById(params.TokenId); err == nil {
|
|
|
|
|
|
tokenName = token.Name
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
log := &Log{
|
|
|
|
|
|
UserId: params.UserId,
|
|
|
|
|
|
Username: username,
|
|
|
|
|
|
CreatedAt: common.GetTimestamp(),
|
|
|
|
|
|
Type: params.LogType,
|
|
|
|
|
|
Content: params.Content,
|
|
|
|
|
|
TokenName: tokenName,
|
|
|
|
|
|
ModelName: params.ModelName,
|
|
|
|
|
|
Quota: params.Quota,
|
|
|
|
|
|
ChannelId: params.ChannelId,
|
|
|
|
|
|
TokenId: params.TokenId,
|
|
|
|
|
|
Group: params.Group,
|
|
|
|
|
|
Other: common.MapToJsonStr(params.Other),
|
|
|
|
|
|
}
|
|
|
|
|
|
err := LOG_DB.Create(log).Error
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
common.SysLog("failed to record task billing log: " + err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-04 02:12:18 +08:00
|
|
|
|
func GetAllLogs(logType int, startTimestamp int64, endTimestamp int64, modelName string, username string, tokenName string, startIdx int, num int, channel int, group string, requestId string) (logs []*Log, total int64, err error) {
|
2023-06-10 20:40:07 +08:00
|
|
|
|
var tx *gorm.DB
|
|
|
|
|
|
if logType == LogTypeUnknown {
|
2024-08-13 10:29:55 +08:00
|
|
|
|
tx = LOG_DB
|
2023-06-10 20:40:07 +08:00
|
|
|
|
} else {
|
2025-01-22 13:37:32 +08:00
|
|
|
|
tx = LOG_DB.Where("logs.type = ?", logType)
|
2023-06-10 20:40:07 +08:00
|
|
|
|
}
|
2025-01-09 15:20:12 +08:00
|
|
|
|
|
2023-06-24 15:28:11 +08:00
|
|
|
|
if modelName != "" {
|
2025-01-22 13:37:32 +08:00
|
|
|
|
tx = tx.Where("logs.model_name like ?", modelName)
|
2023-06-24 15:28:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
if username != "" {
|
2025-01-22 13:37:32 +08:00
|
|
|
|
tx = tx.Where("logs.username = ?", username)
|
2023-06-24 15:28:11 +08:00
|
|
|
|
}
|
2023-06-24 19:13:33 +08:00
|
|
|
|
if tokenName != "" {
|
2025-01-22 13:37:32 +08:00
|
|
|
|
tx = tx.Where("logs.token_name = ?", tokenName)
|
2023-06-24 19:13:33 +08:00
|
|
|
|
}
|
2026-02-04 02:12:18 +08:00
|
|
|
|
if requestId != "" {
|
|
|
|
|
|
tx = tx.Where("logs.request_id = ?", requestId)
|
|
|
|
|
|
}
|
2023-06-24 15:28:11 +08:00
|
|
|
|
if startTimestamp != 0 {
|
2025-01-22 13:37:32 +08:00
|
|
|
|
tx = tx.Where("logs.created_at >= ?", startTimestamp)
|
2023-06-24 15:28:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
if endTimestamp != 0 {
|
2025-01-22 13:37:32 +08:00
|
|
|
|
tx = tx.Where("logs.created_at <= ?", endTimestamp)
|
2023-06-24 15:28:11 +08:00
|
|
|
|
}
|
2023-09-17 19:18:16 +08:00
|
|
|
|
if channel != 0 {
|
2025-01-22 13:37:32 +08:00
|
|
|
|
tx = tx.Where("logs.channel_id = ?", channel)
|
2023-09-17 19:18:16 +08:00
|
|
|
|
}
|
2024-12-24 14:48:11 +08:00
|
|
|
|
if group != "" {
|
2025-06-14 17:51:05 +08:00
|
|
|
|
tx = tx.Where("logs."+logGroupCol+" = ?", group)
|
2024-12-24 14:48:11 +08:00
|
|
|
|
}
|
2024-08-11 11:21:34 +08:00
|
|
|
|
err = tx.Model(&Log{}).Count(&total).Error
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
|
}
|
2025-01-22 13:37:32 +08:00
|
|
|
|
err = tx.Order("logs.id desc").Limit(num).Offset(startIdx).Find(&logs).Error
|
2024-08-11 11:21:34 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
|
}
|
2025-02-12 19:19:13 +08:00
|
|
|
|
|
2025-08-16 19:11:15 +08:00
|
|
|
|
channelIds := types.NewSet[int]()
|
2025-02-12 19:19:13 +08:00
|
|
|
|
for _, log := range logs {
|
|
|
|
|
|
if log.ChannelId != 0 {
|
2025-08-16 19:11:15 +08:00
|
|
|
|
channelIds.Add(log.ChannelId)
|
2025-02-12 19:19:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-14 18:23:25 +08:00
|
|
|
|
|
2025-08-16 19:11:15 +08:00
|
|
|
|
if channelIds.Len() > 0 {
|
2025-02-12 19:19:13 +08:00
|
|
|
|
var channels []struct {
|
|
|
|
|
|
Id int `gorm:"column:id"`
|
|
|
|
|
|
Name string `gorm:"column:name"`
|
|
|
|
|
|
}
|
2026-02-23 14:11:11 +08:00
|
|
|
|
if common.MemoryCacheEnabled {
|
|
|
|
|
|
// Cache get channel
|
|
|
|
|
|
for _, channelId := range channelIds.Items() {
|
|
|
|
|
|
if cacheChannel, err := CacheGetChannel(channelId); err == nil {
|
|
|
|
|
|
channels = append(channels, struct {
|
|
|
|
|
|
Id int `gorm:"column:id"`
|
|
|
|
|
|
Name string `gorm:"column:name"`
|
|
|
|
|
|
}{
|
|
|
|
|
|
Id: channelId,
|
|
|
|
|
|
Name: cacheChannel.Name,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Bulk query channels from DB
|
|
|
|
|
|
if err = DB.Table("channels").Select("id, name").Where("id IN ?", channelIds.Items()).Find(&channels).Error; err != nil {
|
|
|
|
|
|
return logs, total, err
|
|
|
|
|
|
}
|
2025-02-12 19:19:13 +08:00
|
|
|
|
}
|
2025-08-16 19:11:15 +08:00
|
|
|
|
channelMap := make(map[int]string, len(channels))
|
2025-02-12 19:19:13 +08:00
|
|
|
|
for _, channel := range channels {
|
|
|
|
|
|
channelMap[channel.Id] = channel.Name
|
|
|
|
|
|
}
|
|
|
|
|
|
for i := range logs {
|
|
|
|
|
|
logs[i].ChannelName = channelMap[logs[i].ChannelId]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-11 11:21:34 +08:00
|
|
|
|
return logs, total, err
|
2023-06-09 16:59:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 21:26:26 +08:00
|
|
|
|
const logSearchCountLimit = 10000
|
|
|
|
|
|
|
2026-02-04 02:12:18 +08:00
|
|
|
|
func GetUserLogs(userId int, logType int, startTimestamp int64, endTimestamp int64, modelName string, tokenName string, startIdx int, num int, group string, requestId string) (logs []*Log, total int64, err error) {
|
2023-06-10 16:04:04 +08:00
|
|
|
|
var tx *gorm.DB
|
|
|
|
|
|
if logType == LogTypeUnknown {
|
2025-01-22 13:37:32 +08:00
|
|
|
|
tx = LOG_DB.Where("logs.user_id = ?", userId)
|
2023-06-10 16:04:04 +08:00
|
|
|
|
} else {
|
2025-01-22 13:37:32 +08:00
|
|
|
|
tx = LOG_DB.Where("logs.user_id = ? and logs.type = ?", userId, logType)
|
2023-06-10 16:04:04 +08:00
|
|
|
|
}
|
2025-01-09 15:20:12 +08:00
|
|
|
|
|
2023-06-24 15:28:11 +08:00
|
|
|
|
if modelName != "" {
|
2026-02-06 21:26:26 +08:00
|
|
|
|
modelNamePattern, err := sanitizeLikePattern(modelName)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
|
}
|
|
|
|
|
|
tx = tx.Where("logs.model_name LIKE ? ESCAPE '!'", modelNamePattern)
|
2023-06-24 15:28:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
if tokenName != "" {
|
2025-01-22 13:37:32 +08:00
|
|
|
|
tx = tx.Where("logs.token_name = ?", tokenName)
|
2023-06-24 15:28:11 +08:00
|
|
|
|
}
|
2026-02-04 02:12:18 +08:00
|
|
|
|
if requestId != "" {
|
|
|
|
|
|
tx = tx.Where("logs.request_id = ?", requestId)
|
|
|
|
|
|
}
|
2023-06-24 15:28:11 +08:00
|
|
|
|
if startTimestamp != 0 {
|
2025-01-22 13:37:32 +08:00
|
|
|
|
tx = tx.Where("logs.created_at >= ?", startTimestamp)
|
2023-06-24 15:28:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
if endTimestamp != 0 {
|
2025-01-22 13:37:32 +08:00
|
|
|
|
tx = tx.Where("logs.created_at <= ?", endTimestamp)
|
2023-06-24 15:28:11 +08:00
|
|
|
|
}
|
2024-12-24 14:48:11 +08:00
|
|
|
|
if group != "" {
|
2025-06-14 17:51:05 +08:00
|
|
|
|
tx = tx.Where("logs."+logGroupCol+" = ?", group)
|
2024-12-24 14:48:11 +08:00
|
|
|
|
}
|
2026-02-06 21:26:26 +08:00
|
|
|
|
err = tx.Model(&Log{}).Limit(logSearchCountLimit).Count(&total).Error
|
2024-08-14 22:43:57 +08:00
|
|
|
|
if err != nil {
|
2026-02-06 21:26:26 +08:00
|
|
|
|
common.SysError("failed to count user logs: " + err.Error())
|
|
|
|
|
|
return nil, 0, errors.New("查询日志失败")
|
2024-08-14 22:43:57 +08:00
|
|
|
|
}
|
2025-01-22 13:37:32 +08:00
|
|
|
|
err = tx.Order("logs.id desc").Limit(num).Offset(startIdx).Find(&logs).Error
|
2025-02-12 19:19:13 +08:00
|
|
|
|
if err != nil {
|
2026-02-06 21:26:26 +08:00
|
|
|
|
common.SysError("failed to search user logs: " + err.Error())
|
|
|
|
|
|
return nil, 0, errors.New("查询日志失败")
|
2025-02-12 19:19:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-07 22:51:26 +08:00
|
|
|
|
formatUserLogs(logs, startIdx)
|
2024-08-14 22:43:57 +08:00
|
|
|
|
return logs, total, err
|
2023-06-09 16:59:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-25 00:40:11 +08:00
|
|
|
|
type Stat struct {
|
|
|
|
|
|
Quota int `json:"quota"`
|
|
|
|
|
|
Rpm int `json:"rpm"`
|
|
|
|
|
|
Tpm int `json:"tpm"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 21:26:26 +08:00
|
|
|
|
func SumUsedQuota(logType int, startTimestamp int64, endTimestamp int64, modelName string, username string, tokenName string, channel int, group string) (stat Stat, err error) {
|
2024-08-13 10:29:55 +08:00
|
|
|
|
tx := LOG_DB.Table("logs").Select("sum(quota) quota")
|
2024-08-01 16:13:08 +08:00
|
|
|
|
|
|
|
|
|
|
// 为rpm和tpm创建单独的查询
|
2024-08-13 10:29:55 +08:00
|
|
|
|
rpmTpmQuery := LOG_DB.Table("logs").Select("count(*) rpm, sum(prompt_tokens) + sum(completion_tokens) tpm")
|
2024-08-01 16:13:08 +08:00
|
|
|
|
|
2023-06-24 15:28:11 +08:00
|
|
|
|
if username != "" {
|
|
|
|
|
|
tx = tx.Where("username = ?", username)
|
2024-08-01 16:13:08 +08:00
|
|
|
|
rpmTpmQuery = rpmTpmQuery.Where("username = ?", username)
|
2023-06-24 15:28:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
if tokenName != "" {
|
|
|
|
|
|
tx = tx.Where("token_name = ?", tokenName)
|
2024-08-01 16:13:08 +08:00
|
|
|
|
rpmTpmQuery = rpmTpmQuery.Where("token_name = ?", tokenName)
|
2023-06-24 15:28:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
if startTimestamp != 0 {
|
|
|
|
|
|
tx = tx.Where("created_at >= ?", startTimestamp)
|
|
|
|
|
|
}
|
|
|
|
|
|
if endTimestamp != 0 {
|
|
|
|
|
|
tx = tx.Where("created_at <= ?", endTimestamp)
|
|
|
|
|
|
}
|
|
|
|
|
|
if modelName != "" {
|
2026-02-06 21:26:26 +08:00
|
|
|
|
modelNamePattern, err := sanitizeLikePattern(modelName)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return stat, err
|
|
|
|
|
|
}
|
|
|
|
|
|
tx = tx.Where("model_name LIKE ? ESCAPE '!'", modelNamePattern)
|
|
|
|
|
|
rpmTpmQuery = rpmTpmQuery.Where("model_name LIKE ? ESCAPE '!'", modelNamePattern)
|
2023-06-24 15:28:11 +08:00
|
|
|
|
}
|
2023-09-17 19:18:16 +08:00
|
|
|
|
if channel != 0 {
|
2023-11-10 01:59:01 +08:00
|
|
|
|
tx = tx.Where("channel_id = ?", channel)
|
2024-08-01 16:13:08 +08:00
|
|
|
|
rpmTpmQuery = rpmTpmQuery.Where("channel_id = ?", channel)
|
2023-09-17 19:18:16 +08:00
|
|
|
|
}
|
2024-12-24 14:48:11 +08:00
|
|
|
|
if group != "" {
|
2025-06-14 17:51:05 +08:00
|
|
|
|
tx = tx.Where(logGroupCol+" = ?", group)
|
|
|
|
|
|
rpmTpmQuery = rpmTpmQuery.Where(logGroupCol+" = ?", group)
|
2024-12-24 14:48:11 +08:00
|
|
|
|
}
|
2024-08-01 16:13:08 +08:00
|
|
|
|
|
|
|
|
|
|
tx = tx.Where("type = ?", LogTypeConsume)
|
|
|
|
|
|
rpmTpmQuery = rpmTpmQuery.Where("type = ?", LogTypeConsume)
|
|
|
|
|
|
|
|
|
|
|
|
// 只统计最近60秒的rpm和tpm
|
|
|
|
|
|
rpmTpmQuery = rpmTpmQuery.Where("created_at >= ?", time.Now().Add(-60*time.Second).Unix())
|
|
|
|
|
|
|
|
|
|
|
|
// 执行查询
|
2026-02-06 21:26:26 +08:00
|
|
|
|
if err := tx.Scan(&stat).Error; err != nil {
|
|
|
|
|
|
common.SysError("failed to query log stat: " + err.Error())
|
|
|
|
|
|
return stat, errors.New("查询统计数据失败")
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := rpmTpmQuery.Scan(&stat).Error; err != nil {
|
|
|
|
|
|
common.SysError("failed to query rpm/tpm stat: " + err.Error())
|
|
|
|
|
|
return stat, errors.New("查询统计数据失败")
|
|
|
|
|
|
}
|
2024-08-01 16:13:08 +08:00
|
|
|
|
|
2026-02-06 21:26:26 +08:00
|
|
|
|
return stat, nil
|
2023-06-24 15:28:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func SumUsedToken(logType int, startTimestamp int64, endTimestamp int64, modelName string, username string, tokenName string) (token int) {
|
2024-08-13 10:29:55 +08:00
|
|
|
|
tx := LOG_DB.Table("logs").Select("ifnull(sum(prompt_tokens),0) + ifnull(sum(completion_tokens),0)")
|
2023-06-24 15:28:11 +08:00
|
|
|
|
if username != "" {
|
|
|
|
|
|
tx = tx.Where("username = ?", username)
|
|
|
|
|
|
}
|
|
|
|
|
|
if tokenName != "" {
|
|
|
|
|
|
tx = tx.Where("token_name = ?", tokenName)
|
|
|
|
|
|
}
|
|
|
|
|
|
if startTimestamp != 0 {
|
|
|
|
|
|
tx = tx.Where("created_at >= ?", startTimestamp)
|
|
|
|
|
|
}
|
|
|
|
|
|
if endTimestamp != 0 {
|
|
|
|
|
|
tx = tx.Where("created_at <= ?", endTimestamp)
|
|
|
|
|
|
}
|
|
|
|
|
|
if modelName != "" {
|
|
|
|
|
|
tx = tx.Where("model_name = ?", modelName)
|
|
|
|
|
|
}
|
|
|
|
|
|
tx.Where("type = ?", LogTypeConsume).Scan(&token)
|
|
|
|
|
|
return token
|
|
|
|
|
|
}
|
2023-09-17 17:09:56 +08:00
|
|
|
|
|
2025-04-10 22:52:17 +08:00
|
|
|
|
func DeleteOldLog(ctx context.Context, targetTimestamp int64, limit int) (int64, error) {
|
|
|
|
|
|
var total int64 = 0
|
|
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
|
if nil != ctx.Err() {
|
|
|
|
|
|
return total, ctx.Err()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
result := LOG_DB.Where("created_at < ?", targetTimestamp).Limit(limit).Delete(&Log{})
|
|
|
|
|
|
if nil != result.Error {
|
|
|
|
|
|
return total, result.Error
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
total += result.RowsAffected
|
|
|
|
|
|
|
|
|
|
|
|
if result.RowsAffected < int64(limit) {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return total, nil
|
2023-09-17 17:09:56 +08:00
|
|
|
|
}
|