one-api/model/log.go
2024-05-29 01:04:23 +08:00

278 lines
8.4 KiB
Go

package model
import (
"context"
"fmt"
"one-api/common"
"one-api/common/logger"
"one-api/common/utils"
"gorm.io/gorm"
)
type Log struct {
Id int `json:"id"`
UserId int `json:"user_id" gorm:"index"`
CreatedAt int64 `json:"created_at" gorm:"bigint;index:idx_created_at_type"`
Type int `json:"type" gorm:"index:idx_created_at_type"`
Content string `json:"content"`
Username string `json:"username" gorm:"index:index_username_model_name,priority:2;default:''"`
TokenName string `json:"token_name" gorm:"index;default:''"`
ModelName string `json:"model_name" gorm:"index;index:index_username_model_name,priority:1;default:''"`
Quota int `json:"quota" gorm:"default:0"`
PromptTokens int `json:"prompt_tokens" gorm:"default:0"`
CompletionTokens int `json:"completion_tokens" gorm:"default:0"`
ChannelId int `json:"channel_id" gorm:"index"`
RequestTime int `json:"request_time" gorm:"default:0"`
Channel *Channel `json:"channel" gorm:"foreignKey:Id;references:ChannelId"`
}
const (
LogTypeUnknown = iota
LogTypeTopup
LogTypeConsume
LogTypeManage
LogTypeSystem
)
func RecordLog(userId int, logType int, content string) {
if logType == LogTypeConsume && !common.LogConsumeEnabled {
return
}
log := &Log{
UserId: userId,
Username: GetUsernameById(userId),
CreatedAt: utils.GetTimestamp(),
Type: logType,
Content: content,
}
err := DB.Create(log).Error
if err != nil {
logger.SysError("failed to record log: " + err.Error())
}
}
func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptTokens int, completionTokens int, modelName string, tokenName string, quota int, content string, requestTime int) {
logger.LogInfo(ctx, fmt.Sprintf("record consume log: userId=%d, channelId=%d, promptTokens=%d, completionTokens=%d, modelName=%s, tokenName=%s, quota=%d, content=%s", userId, channelId, promptTokens, completionTokens, modelName, tokenName, quota, content))
if !common.LogConsumeEnabled {
return
}
log := &Log{
UserId: userId,
Username: GetUsernameById(userId),
CreatedAt: utils.GetTimestamp(),
Type: LogTypeConsume,
Content: content,
PromptTokens: promptTokens,
CompletionTokens: completionTokens,
TokenName: tokenName,
ModelName: modelName,
Quota: quota,
ChannelId: channelId,
RequestTime: requestTime,
}
err := DB.Create(log).Error
if err != nil {
logger.LogError(ctx, "failed to record log: "+err.Error())
}
}
type LogsListParams struct {
PaginationParams
LogType int `form:"log_type"`
StartTimestamp int64 `form:"start_timestamp"`
EndTimestamp int64 `form:"end_timestamp"`
ModelName string `form:"model_name"`
Username string `form:"username"`
TokenName string `form:"token_name"`
ChannelId int `form:"channel_id"`
}
var allowedLogsOrderFields = map[string]bool{
"created_at": true,
"channel_id": true,
"user_id": true,
"token_name": true,
"model_name": true,
"type": true,
}
func GetLogsList(params *LogsListParams) (*DataResult[Log], error) {
var tx *gorm.DB
var logs []*Log
tx = DB.Preload("Channel", func(db *gorm.DB) *gorm.DB {
return db.Select("id, name")
})
if params.LogType != LogTypeUnknown {
tx = tx.Where("type = ?", params.LogType)
}
if params.ModelName != "" {
tx = tx.Where("model_name = ?", params.ModelName)
}
if params.Username != "" {
tx = tx.Where("username = ?", params.Username)
}
if params.TokenName != "" {
tx = tx.Where("token_name = ?", params.TokenName)
}
if params.StartTimestamp != 0 {
tx = tx.Where("created_at >= ?", params.StartTimestamp)
}
if params.EndTimestamp != 0 {
tx = tx.Where("created_at <= ?", params.EndTimestamp)
}
if params.ChannelId != 0 {
tx = tx.Where("channel_id = ?", params.ChannelId)
}
return PaginateAndOrder[Log](tx, &params.PaginationParams, &logs, allowedLogsOrderFields)
}
func GetUserLogsList(userId int, params *LogsListParams) (*DataResult[Log], error) {
var logs []*Log
tx := DB.Where("user_id = ?", userId).Omit("id")
if params.LogType != LogTypeUnknown {
tx = DB.Where("type = ?", params.LogType)
}
if params.ModelName != "" {
tx = tx.Where("model_name = ?", params.ModelName)
}
if params.TokenName != "" {
tx = tx.Where("token_name = ?", params.TokenName)
}
if params.StartTimestamp != 0 {
tx = tx.Where("created_at >= ?", params.StartTimestamp)
}
if params.EndTimestamp != 0 {
tx = tx.Where("created_at <= ?", params.EndTimestamp)
}
return PaginateAndOrder[Log](tx, &params.PaginationParams, &logs, allowedLogsOrderFields)
}
func SearchAllLogs(keyword string) (logs []*Log, err error) {
err = DB.Where("type = ? or content LIKE ?", keyword, keyword+"%").Order("id desc").Limit(common.MaxRecentItems).Find(&logs).Error
return logs, err
}
func SearchUserLogs(userId int, keyword string) (logs []*Log, err error) {
err = DB.Where("user_id = ? and type = ?", userId, keyword).Order("id desc").Limit(common.MaxRecentItems).Omit("id").Find(&logs).Error
return logs, err
}
func SumUsedQuota(logType int, startTimestamp int64, endTimestamp int64, modelName string, username string, tokenName string, channel int) (quota int) {
tx := DB.Table("logs").Select(assembleSumSelectStr("quota"))
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)
}
if channel != 0 {
tx = tx.Where("channel_id = ?", channel)
}
tx.Where("type = ?", LogTypeConsume).Scan(&quota)
return quota
}
func SumUsedToken(logType int, startTimestamp int64, endTimestamp int64, modelName string, username string, tokenName string) (token int) {
tx := DB.Table("logs").Select(assembleSumSelectStr("prompt_tokens") + " + " + assembleSumSelectStr("completion_tokens"))
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
}
func DeleteOldLog(targetTimestamp int64) (int64, error) {
result := DB.Where("created_at < ?", targetTimestamp).Delete(&Log{})
return result.RowsAffected, result.Error
}
type LogStatistic struct {
Date string `gorm:"column:date"`
RequestCount int64 `gorm:"column:request_count"`
Quota int64 `gorm:"column:quota"`
PromptTokens int64 `gorm:"column:prompt_tokens"`
CompletionTokens int64 `gorm:"column:completion_tokens"`
RequestTime int64 `gorm:"column:request_time"`
}
type LogStatisticGroupModel struct {
LogStatistic
ModelName string `gorm:"column:model_name"`
}
func GetUserModelExpensesByPeriod(user_id, startTimestamp, endTimestamp int) (LogStatistic []*LogStatisticGroupModel, err error) {
groupSelect := getTimestampGroupsSelect("created_at", "day", "date")
err = DB.Raw(`
SELECT `+groupSelect+`,
model_name, count(1) as request_count,
sum(quota) as quota,
sum(prompt_tokens) as prompt_tokens,
sum(completion_tokens) as completion_tokens
FROM logs
WHERE type=2
AND user_id= ?
AND created_at BETWEEN ? AND ?
GROUP BY date, model_name
ORDER BY date, model_name
`, user_id, startTimestamp, endTimestamp).Scan(&LogStatistic).Error
return
}
type LogStatisticGroupChannel struct {
LogStatistic
Channel string `gorm:"column:channel"`
}
func GetChannelExpensesByPeriod(startTimestamp, endTimestamp int64) (LogStatistics []*LogStatisticGroupChannel, err error) {
groupSelect := getTimestampGroupsSelect("created_at", "day", "date")
err = DB.Raw(`
SELECT `+groupSelect+`,
count(1) as request_count,
sum(quota) as quota,
sum(prompt_tokens) as prompt_tokens,
sum(completion_tokens) as completion_tokens,
sum(request_time) as request_time,
channels.name as channel
FROM logs
JOIN channels ON logs.channel_id = channels.id
WHERE logs.type=2
AND logs.created_at BETWEEN ? AND ?
GROUP BY date, channels.id
ORDER BY date, channels.id
`, startTimestamp, endTimestamp).Scan(&LogStatistics).Error
return LogStatistics, err
}