diff --git a/common/helper/time.go b/common/helper/time.go
index 302746db..f0bc6021 100644
--- a/common/helper/time.go
+++ b/common/helper/time.go
@@ -13,3 +13,8 @@ func GetTimeString() string {
now := time.Now()
return fmt.Sprintf("%s%d", now.Format("20060102150405"), now.UnixNano()%1e9)
}
+
+// CalcElapsedTime return the elapsed time in milliseconds (ms)
+func CalcElapsedTime(start time.Time) int64 {
+ return time.Now().Sub(start).Milliseconds()
+}
diff --git a/model/log.go b/model/log.go
index 1fd7ee84..17525500 100644
--- a/model/log.go
+++ b/model/log.go
@@ -13,19 +13,22 @@ import (
)
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" gorm:"index"`
- RequestId string `json:"request_id"`
+ 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" gorm:"index"`
+ RequestId string `json:"request_id" gorm:"default:''"`
+ ElapsedTime int64 `json:"elapsed_time" gorm:"default:0"` // unit is ms
+ IsStream bool `json:"is_stream" gorm:"default:false"`
+ SystemPromptReset bool `json:"system_prompt_reset" gorm:"default:false"`
}
const (
@@ -73,23 +76,13 @@ func RecordTopupLog(ctx context.Context, userId int, content string, quota int)
recordLogHelper(ctx, log)
}
-func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptTokens int, completionTokens int, modelName string, tokenName string, quota int64, content string) {
+func RecordConsumeLog(ctx context.Context, log *Log) {
if !config.LogConsumeEnabled {
return
}
- log := &Log{
- UserId: userId,
- Username: GetUsernameById(userId),
- CreatedAt: helper.GetTimestamp(),
- Type: LogTypeConsume,
- Content: content,
- PromptTokens: promptTokens,
- CompletionTokens: completionTokens,
- TokenName: tokenName,
- ModelName: modelName,
- Quota: int(quota),
- ChannelId: channelId,
- }
+ log.Username = GetUsernameById(log.UserId)
+ log.CreatedAt = helper.GetTimestamp()
+ log.Type = LogTypeConsume
recordLogHelper(ctx, log)
}
diff --git a/relay/billing/billing.go b/relay/billing/billing.go
index f1bf197a..8bd086fe 100644
--- a/relay/billing/billing.go
+++ b/relay/billing/billing.go
@@ -33,7 +33,16 @@ func PostConsumeQuota(ctx context.Context, tokenId int, quotaDelta int64, totalQ
// totalQuota is total quota consumed
if totalQuota != 0 {
logContent := fmt.Sprintf("%.2f × %.2f", modelRatio, groupRatio)
- model.RecordConsumeLog(ctx, userId, channelId, int(totalQuota), 0, modelName, tokenName, totalQuota, logContent)
+ model.RecordConsumeLog(ctx, &model.Log{
+ UserId: userId,
+ ChannelId: channelId,
+ PromptTokens: int(totalQuota),
+ CompletionTokens: 0,
+ ModelName: modelName,
+ TokenName: tokenName,
+ Quota: int(totalQuota),
+ Content: logContent,
+ })
model.UpdateUserUsedQuotaAndRequestCount(userId, totalQuota)
model.UpdateChannelUsedQuota(channelId, totalQuota)
}
diff --git a/relay/controller/helper.go b/relay/controller/helper.go
index 2b83c3d9..3262c017 100644
--- a/relay/controller/helper.go
+++ b/relay/controller/helper.go
@@ -8,6 +8,7 @@ import (
"net/http"
"strings"
+ "github.com/songquanpeng/one-api/common/helper"
"github.com/songquanpeng/one-api/relay/constant/role"
"github.com/gin-gonic/gin"
@@ -121,12 +122,20 @@ func postConsumeQuota(ctx context.Context, usage *relaymodel.Usage, meta *meta.M
if err != nil {
logger.Error(ctx, "error update user quota cache: "+err.Error())
}
- var extraLog string
- if systemPromptReset {
- extraLog = " (注意系统提示词已被重置)"
- }
- logContent := fmt.Sprintf("%.2f × %.2f × %.2f%s", modelRatio, groupRatio, completionRatio, extraLog)
- model.RecordConsumeLog(ctx, meta.UserId, meta.ChannelId, promptTokens, completionTokens, textRequest.Model, meta.TokenName, quota, logContent)
+ logContent := fmt.Sprintf("%.2f × %.2f × %.2f", modelRatio, groupRatio, completionRatio)
+ model.RecordConsumeLog(ctx, &model.Log{
+ UserId: meta.UserId,
+ ChannelId: meta.ChannelId,
+ PromptTokens: promptTokens,
+ CompletionTokens: completionTokens,
+ ModelName: textRequest.Model,
+ TokenName: meta.TokenName,
+ Quota: int(quota),
+ Content: logContent,
+ IsStream: meta.IsStream,
+ ElapsedTime: helper.CalcElapsedTime(meta.StartTime),
+ SystemPromptReset: systemPromptReset,
+ })
model.UpdateUserUsedQuotaAndRequestCount(meta.UserId, quota)
model.UpdateChannelUsedQuota(meta.ChannelId, quota)
}
diff --git a/relay/controller/image.go b/relay/controller/image.go
index 468da566..24e49969 100644
--- a/relay/controller/image.go
+++ b/relay/controller/image.go
@@ -211,7 +211,16 @@ func RelayImageHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus
if quota != 0 {
tokenName := c.GetString(ctxkey.TokenName)
logContent := fmt.Sprintf("%.2f × %.2f", modelRatio, groupRatio)
- model.RecordConsumeLog(ctx, meta.UserId, meta.ChannelId, 0, 0, imageRequest.Model, tokenName, quota, logContent)
+ model.RecordConsumeLog(ctx, &model.Log{
+ UserId: meta.UserId,
+ ChannelId: meta.ChannelId,
+ PromptTokens: 0,
+ CompletionTokens: 0,
+ ModelName: imageRequest.Model,
+ TokenName: tokenName,
+ Quota: int(quota),
+ Content: logContent,
+ })
model.UpdateUserUsedQuotaAndRequestCount(meta.UserId, quota)
channelId := c.GetInt(ctxkey.ChannelId)
model.UpdateChannelUsedQuota(channelId, quota)
diff --git a/relay/controller/text.go b/relay/controller/text.go
index 9a47c58b..6a61884d 100644
--- a/relay/controller/text.go
+++ b/relay/controller/text.go
@@ -4,11 +4,12 @@ import (
"bytes"
"encoding/json"
"fmt"
- "github.com/songquanpeng/one-api/common/config"
"io"
"net/http"
"github.com/gin-gonic/gin"
+
+ "github.com/songquanpeng/one-api/common/config"
"github.com/songquanpeng/one-api/common/logger"
"github.com/songquanpeng/one-api/relay"
"github.com/songquanpeng/one-api/relay/adaptor"
diff --git a/relay/meta/relay_meta.go b/relay/meta/relay_meta.go
index bcbe1045..6bf070f3 100644
--- a/relay/meta/relay_meta.go
+++ b/relay/meta/relay_meta.go
@@ -1,12 +1,15 @@
package meta
import (
+ "strings"
+ "time"
+
"github.com/gin-gonic/gin"
+
"github.com/songquanpeng/one-api/common/ctxkey"
"github.com/songquanpeng/one-api/model"
"github.com/songquanpeng/one-api/relay/channeltype"
"github.com/songquanpeng/one-api/relay/relaymode"
- "strings"
)
type Meta struct {
@@ -31,6 +34,7 @@ type Meta struct {
RequestURLPath string
PromptTokens int // only for DoResponse
SystemPrompt string
+ StartTime time.Time
}
func GetByContext(c *gin.Context) *Meta {
@@ -48,6 +52,7 @@ func GetByContext(c *gin.Context) *Meta {
APIKey: strings.TrimPrefix(c.Request.Header.Get("Authorization"), "Bearer "),
RequestURLPath: c.Request.URL.String(),
SystemPrompt: c.GetString(ctxkey.SystemPrompt),
+ StartTime: time.Now(),
}
cfg, ok := c.Get(ctxkey.Config)
if ok {
diff --git a/web/default/src/components/LogsTable.js b/web/default/src/components/LogsTable.js
index 8b4cda7b..39bafc2e 100644
--- a/web/default/src/components/LogsTable.js
+++ b/web/default/src/components/LogsTable.js
@@ -1,21 +1,26 @@
import React, { useEffect, useState } from 'react';
-import { Button, Form, Header, Label, Pagination, Segment, Select, Table } from 'semantic-ui-react';
+import {
+ Button,
+ Form,
+ Header,
+ Label,
+ Pagination,
+ Segment,
+ Select,
+ Table,
+} from 'semantic-ui-react';
import { API, isAdmin, showError, timestamp2string } from '../helpers';
import { ITEMS_PER_PAGE } from '../constants';
import { renderQuota } from '../helpers/render';
function renderTimestamp(timestamp) {
- return (
- <>
- {timestamp2string(timestamp)}
- >
- );
+ return <>{timestamp2string(timestamp)}>;
}
const MODE_OPTIONS = [
{ key: 'all', text: '全部用户', value: 'all' },
- { key: 'self', text: '当前用户', value: 'self' }
+ { key: 'self', text: '当前用户', value: 'self' },
];
const LOG_OPTIONS = [
@@ -23,24 +28,87 @@ const LOG_OPTIONS = [
{ key: '1', text: '充值', value: 1 },
{ key: '2', text: '消费', value: 2 },
{ key: '3', text: '管理', value: 3 },
- { key: '4', text: '系统', value: 4 }
+ { key: '4', text: '系统', value: 4 },
];
function renderType(type) {
switch (type) {
case 1:
- return ;
+ return (
+
+ );
case 2:
- return ;
+ return (
+
+ );
case 3:
- return ;
+ return (
+
+ );
case 4:
- return ;
+ return (
+
+ );
default:
- return ;
+ return (
+
+ );
}
}
+function getColorByElapsedTime(elapsedTime) {
+ if (elapsedTime === undefined || 0) return 'black';
+ if (elapsedTime < 1000) return 'green';
+ if (elapsedTime < 3000) return 'olive';
+ if (elapsedTime < 5000) return 'yellow';
+ if (elapsedTime < 10000) return 'orange';
+ return 'red';
+}
+
+function renderDetail(log) {
+ return (
+ <>
+ 倍率:{log.content}
+
+ {log.elapsed_time && (
+
+ )}
+ {log.is_stream && (
+ <>
+
+ >
+ )}
+ {log.system_prompt_reset && (
+ <>
+
+ >
+ )}
+
+ {log.request_id}
+ >
+ );
+}
+
const LogsTable = () => {
const [logs, setLogs] = useState([]);
const [showStat, setShowStat] = useState(false);
@@ -57,13 +125,20 @@ const LogsTable = () => {
model_name: '',
start_timestamp: timestamp2string(0),
end_timestamp: timestamp2string(now.getTime() / 1000 + 3600),
- channel: ''
+ channel: '',
});
- const { username, token_name, model_name, start_timestamp, end_timestamp, channel } = inputs;
+ const {
+ username,
+ token_name,
+ model_name,
+ start_timestamp,
+ end_timestamp,
+ channel,
+ } = inputs;
const [stat, setStat] = useState({
quota: 0,
- token: 0
+ token: 0,
});
const handleInputChange = (e, { name, value }) => {
@@ -73,7 +148,9 @@ const LogsTable = () => {
const getLogSelfStat = async () => {
let localStartTimestamp = Date.parse(start_timestamp) / 1000;
let localEndTimestamp = Date.parse(end_timestamp) / 1000;
- let res = await API.get(`/api/log/self/stat?type=${logType}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`);
+ let res = await API.get(
+ `/api/log/self/stat?type=${logType}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`
+ );
const { success, message, data } = res.data;
if (success) {
setStat(data);
@@ -85,7 +162,9 @@ const LogsTable = () => {
const getLogStat = async () => {
let localStartTimestamp = Date.parse(start_timestamp) / 1000;
let localEndTimestamp = Date.parse(end_timestamp) / 1000;
- let res = await API.get(`/api/log/stat?type=${logType}&username=${username}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&channel=${channel}`);
+ let res = await API.get(
+ `/api/log/stat?type=${logType}&username=${username}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&channel=${channel}`
+ );
const { success, message, data } = res.data;
if (success) {
setStat(data);
@@ -201,37 +280,82 @@ const LogsTable = () => {