feat: add error logging functionality to relay and update logs table for error type display
This commit is contained in:
@@ -39,6 +39,26 @@ func relayHandler(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusCode
|
|||||||
default:
|
default:
|
||||||
err = relay.TextHelper(c)
|
err = relay.TextHelper(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// 保存错误日志到mysql中
|
||||||
|
userId := c.GetInt("id")
|
||||||
|
tokenName := c.GetString("token_name")
|
||||||
|
modelName := c.GetString("original_model")
|
||||||
|
tokenId := c.GetInt("token_id")
|
||||||
|
userGroup := c.GetString("group")
|
||||||
|
channelId := c.GetInt("channel_id")
|
||||||
|
other := make(map[string]interface{})
|
||||||
|
other["error_type"] = err.Error.Type
|
||||||
|
other["error_code"] = err.Error.Code
|
||||||
|
other["status_code"] = err.StatusCode
|
||||||
|
other["channel_id"] = channelId
|
||||||
|
other["channel_name"] = c.GetString("channel_name")
|
||||||
|
other["channel_type"] = c.GetInt("channel_type")
|
||||||
|
|
||||||
|
model.RecordErrorLog(c, userId, channelId, modelName, tokenName, err.Error.Message, tokenId, 0, false, userGroup, other)
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
30
model/log.go
30
model/log.go
@@ -40,6 +40,7 @@ const (
|
|||||||
LogTypeConsume
|
LogTypeConsume
|
||||||
LogTypeManage
|
LogTypeManage
|
||||||
LogTypeSystem
|
LogTypeSystem
|
||||||
|
LogTypeError
|
||||||
)
|
)
|
||||||
|
|
||||||
func formatUserLogs(logs []*Log) {
|
func formatUserLogs(logs []*Log) {
|
||||||
@@ -88,6 +89,35 @@ func RecordLog(userId int, logType int, content string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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{}) {
|
||||||
|
common.LogInfo(c, fmt.Sprintf("record error log: userId=%d, channelId=%d, modelName=%s, tokenName=%s, content=%s", userId, channelId, modelName, tokenName, content))
|
||||||
|
username := c.GetString("username")
|
||||||
|
otherStr := common.MapToJsonStr(other)
|
||||||
|
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,
|
||||||
|
Other: otherStr,
|
||||||
|
}
|
||||||
|
err := LOG_DB.Create(log).Error
|
||||||
|
if err != nil {
|
||||||
|
common.LogError(c, "failed to record log: "+err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func RecordConsumeLog(c *gin.Context, userId int, channelId int, promptTokens int, completionTokens int,
|
func RecordConsumeLog(c *gin.Context, userId int, channelId int, promptTokens int, completionTokens int,
|
||||||
modelName string, tokenName string, quota int, content string, tokenId int, userQuota int, useTimeSeconds int,
|
modelName string, tokenName string, quota int, content string, tokenId int, userQuota int, useTimeSeconds int,
|
||||||
isStream bool, group string, other map[string]interface{}) {
|
isStream bool, group string, other map[string]interface{}) {
|
||||||
|
|||||||
@@ -85,8 +85,10 @@ const LogsTable = () => {
|
|||||||
return <Tag color='orange' size='large'>{t('管理')}</Tag>;
|
return <Tag color='orange' size='large'>{t('管理')}</Tag>;
|
||||||
case 4:
|
case 4:
|
||||||
return <Tag color='purple' size='large'>{t('系统')}</Tag>;
|
return <Tag color='purple' size='large'>{t('系统')}</Tag>;
|
||||||
|
case 5:
|
||||||
|
return <Tag color='red' size='large'>{t('错误')}</Tag>;
|
||||||
default:
|
default:
|
||||||
return <Tag color='black' size='large'>{t('未知')}</Tag>;
|
return <Tag color='grey' size='large'>{t('未知')}</Tag>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +162,7 @@ const LogsTable = () => {
|
|||||||
color={stringToColor(record.model_name)}
|
color={stringToColor(record.model_name)}
|
||||||
size='large'
|
size='large'
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
copyText(event, record.model_name).then(r => {});
|
copyText(event, record.model_name).then(r => { });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{' '}{record.model_name}{' '}
|
{' '}{record.model_name}{' '}
|
||||||
@@ -170,13 +172,13 @@ const LogsTable = () => {
|
|||||||
<>
|
<>
|
||||||
<Space vertical align={'start'}>
|
<Space vertical align={'start'}>
|
||||||
<Popover content={
|
<Popover content={
|
||||||
<div style={{padding: 10}}>
|
<div style={{ padding: 10 }}>
|
||||||
<Space vertical align={'start'}>
|
<Space vertical align={'start'}>
|
||||||
<Tag
|
<Tag
|
||||||
color={stringToColor(record.model_name)}
|
color={stringToColor(record.model_name)}
|
||||||
size='large'
|
size='large'
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
copyText(event, record.model_name).then(r => {});
|
copyText(event, record.model_name).then(r => { });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('请求并计费模型')}{' '}{record.model_name}{' '}
|
{t('请求并计费模型')}{' '}{record.model_name}{' '}
|
||||||
@@ -185,7 +187,7 @@ const LogsTable = () => {
|
|||||||
color={stringToColor(other.upstream_model_name)}
|
color={stringToColor(other.upstream_model_name)}
|
||||||
size='large'
|
size='large'
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
copyText(event, other.upstream_model_name).then(r => {});
|
copyText(event, other.upstream_model_name).then(r => { });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('实际模型')}{' '}{other.upstream_model_name}{' '}
|
{t('实际模型')}{' '}{other.upstream_model_name}{' '}
|
||||||
@@ -197,9 +199,9 @@ const LogsTable = () => {
|
|||||||
color={stringToColor(record.model_name)}
|
color={stringToColor(record.model_name)}
|
||||||
size='large'
|
size='large'
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
copyText(event, record.model_name).then(r => {});
|
copyText(event, record.model_name).then(r => { });
|
||||||
}}
|
}}
|
||||||
suffixIcon={<IconRefresh style={{width: '0.8em', height: '0.8em', opacity: 0.6}} />}
|
suffixIcon={<IconRefresh style={{ width: '0.8em', height: '0.8em', opacity: 0.6 }} />}
|
||||||
>
|
>
|
||||||
{' '}{record.model_name}{' '}
|
{' '}{record.model_name}{' '}
|
||||||
</Tag>
|
</Tag>
|
||||||
@@ -325,7 +327,7 @@ const LogsTable = () => {
|
|||||||
className: isAdmin() ? 'tableShow' : 'tableHiddle',
|
className: isAdmin() ? 'tableShow' : 'tableHiddle',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return isAdminUser ? (
|
return isAdminUser ? (
|
||||||
record.type === 0 || record.type === 2 ? (
|
(record.type === 0 || record.type === 2 || record.type === 5) ? (
|
||||||
<div>
|
<div>
|
||||||
{
|
{
|
||||||
<Tooltip content={record.channel_name || '[未知]'}>
|
<Tooltip content={record.channel_name || '[未知]'}>
|
||||||
@@ -378,7 +380,7 @@ const LogsTable = () => {
|
|||||||
title: t('令牌'),
|
title: t('令牌'),
|
||||||
dataIndex: 'token_name',
|
dataIndex: 'token_name',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return record.type === 0 || record.type === 2 ? (
|
return (record.type === 0 || record.type === 2 || record.type === 5) ? (
|
||||||
<div>
|
<div>
|
||||||
<Tag
|
<Tag
|
||||||
color='grey'
|
color='grey'
|
||||||
@@ -402,33 +404,33 @@ const LogsTable = () => {
|
|||||||
title: t('分组'),
|
title: t('分组'),
|
||||||
dataIndex: 'group',
|
dataIndex: 'group',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
if (record.type === 0 || record.type === 2) {
|
if (record.type === 0 || record.type === 2 || record.type === 5) {
|
||||||
if (record.group) {
|
if (record.group) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderGroup(record.group)}
|
{renderGroup(record.group)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let other = null;
|
let other = null;
|
||||||
try {
|
try {
|
||||||
other = JSON.parse(record.other);
|
other = JSON.parse(record.other);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Failed to parse record.other: "${record.other}".`, e);
|
console.error(`Failed to parse record.other: "${record.other}".`, e);
|
||||||
}
|
}
|
||||||
if (other === null) {
|
if (other === null) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
if (other.group !== undefined) {
|
if (other.group !== undefined) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderGroup(other.group)}
|
{renderGroup(other.group)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
@@ -447,7 +449,7 @@ const LogsTable = () => {
|
|||||||
title: t('模型'),
|
title: t('模型'),
|
||||||
dataIndex: 'model_name',
|
dataIndex: 'model_name',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return record.type === 0 || record.type === 2 ? (
|
return (record.type === 0 || record.type === 2 || record.type === 5) ? (
|
||||||
<>{renderModelName(record)}</>
|
<>{renderModelName(record)}</>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
@@ -487,7 +489,7 @@ const LogsTable = () => {
|
|||||||
title: t('提示'),
|
title: t('提示'),
|
||||||
dataIndex: 'prompt_tokens',
|
dataIndex: 'prompt_tokens',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return record.type === 0 || record.type === 2 ? (
|
return (record.type === 0 || record.type === 2 || record.type === 5) ? (
|
||||||
<>{<span> {text} </span>}</>
|
<>{<span> {text} </span>}</>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
@@ -500,7 +502,7 @@ const LogsTable = () => {
|
|||||||
dataIndex: 'completion_tokens',
|
dataIndex: 'completion_tokens',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return parseInt(text) > 0 &&
|
return parseInt(text) > 0 &&
|
||||||
(record.type === 0 || record.type === 2) ? (
|
(record.type === 0 || record.type === 2 || record.type === 5) ? (
|
||||||
<>{<span> {text} </span>}</>
|
<>{<span> {text} </span>}</>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
@@ -512,7 +514,7 @@ const LogsTable = () => {
|
|||||||
title: t('花费'),
|
title: t('花费'),
|
||||||
dataIndex: 'quota',
|
dataIndex: 'quota',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return record.type === 0 || record.type === 2 ? (
|
return (record.type === 0 || record.type === 2 || record.type === 5) ? (
|
||||||
<>{renderQuota(text, 6)}</>
|
<>{renderQuota(text, 6)}</>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
@@ -588,14 +590,14 @@ const LogsTable = () => {
|
|||||||
other.cache_ratio || 1.0,
|
other.cache_ratio || 1.0,
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Paragraph
|
<Paragraph
|
||||||
ellipsis={{
|
ellipsis={{
|
||||||
rows: 2,
|
rows: 2,
|
||||||
}}
|
}}
|
||||||
style={{ maxWidth: 240 }}
|
style={{ maxWidth: 240 }}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -650,8 +652,8 @@ const LogsTable = () => {
|
|||||||
{allColumns.map(column => {
|
{allColumns.map(column => {
|
||||||
// Skip admin-only columns for non-admin users
|
// Skip admin-only columns for non-admin users
|
||||||
if (!isAdminUser && (column.key === COLUMN_KEYS.CHANNEL ||
|
if (!isAdminUser && (column.key === COLUMN_KEYS.CHANNEL ||
|
||||||
column.key === COLUMN_KEYS.USERNAME ||
|
column.key === COLUMN_KEYS.USERNAME ||
|
||||||
column.key === COLUMN_KEYS.RETRY)) {
|
column.key === COLUMN_KEYS.RETRY)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -962,7 +964,7 @@ const LogsTable = () => {
|
|||||||
|
|
||||||
const handlePageChange = (page) => {
|
const handlePageChange = (page) => {
|
||||||
setActivePage(page);
|
setActivePage(page);
|
||||||
loadLogs(page, pageSize, logType).then((r) => {});
|
loadLogs(page, pageSize, logType).then((r) => { });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePageSizeChange = async (size) => {
|
const handlePageSizeChange = async (size) => {
|
||||||
@@ -1046,7 +1048,7 @@ const LogsTable = () => {
|
|||||||
<>
|
<>
|
||||||
<Form.Section>
|
<Form.Section>
|
||||||
<div style={{ marginBottom: 10 }}>
|
<div style={{ marginBottom: 10 }}>
|
||||||
{
|
{
|
||||||
styleState.isMobile ? (
|
styleState.isMobile ? (
|
||||||
<div>
|
<div>
|
||||||
<Form.DatePicker
|
<Form.DatePicker
|
||||||
@@ -1146,20 +1148,21 @@ const LogsTable = () => {
|
|||||||
<Form.Section></Form.Section>
|
<Form.Section></Form.Section>
|
||||||
</>
|
</>
|
||||||
</Form>
|
</Form>
|
||||||
<div style={{marginTop:10}}>
|
<div style={{ marginTop: 10 }}>
|
||||||
<Select
|
<Select
|
||||||
defaultValue='0'
|
defaultValue='0'
|
||||||
style={{ width: 120 }}
|
style={{ width: 120 }}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setLogType(parseInt(value));
|
setLogType(parseInt(value));
|
||||||
loadLogs(0, pageSize, parseInt(value));
|
loadLogs(0, pageSize, parseInt(value));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Select.Option value='0'>{t('全部')}</Select.Option>
|
<Select.Option value='0'>{t('全部')}</Select.Option>
|
||||||
<Select.Option value='1'>{t('充值')}</Select.Option>
|
<Select.Option value='1'>{t('充值')}</Select.Option>
|
||||||
<Select.Option value='2'>{t('消费')}</Select.Option>
|
<Select.Option value='2'>{t('消费')}</Select.Option>
|
||||||
<Select.Option value='3'>{t('管理')}</Select.Option>
|
<Select.Option value='3'>{t('管理')}</Select.Option>
|
||||||
<Select.Option value='4'>{t('系统')}</Select.Option>
|
<Select.Option value='4'>{t('系统')}</Select.Option>
|
||||||
|
<Select.Option value='5'>{t('错误')}</Select.Option>
|
||||||
</Select>
|
</Select>
|
||||||
<Button
|
<Button
|
||||||
theme='light'
|
theme='light'
|
||||||
|
|||||||
Reference in New Issue
Block a user