diff --git a/controller/relay.go b/controller/relay.go index fb4c524f..4f905fc1 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -39,6 +39,26 @@ func relayHandler(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusCode default: 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 } diff --git a/model/log.go b/model/log.go index 86850a55..fbae4528 100644 --- a/model/log.go +++ b/model/log.go @@ -40,6 +40,7 @@ const ( LogTypeConsume LogTypeManage LogTypeSystem + LogTypeError ) 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, modelName string, tokenName string, quota int, content string, tokenId int, userQuota int, useTimeSeconds int, isStream bool, group string, other map[string]interface{}) { diff --git a/web/src/components/LogsTable.js b/web/src/components/LogsTable.js index 21d0a979..4ad40707 100644 --- a/web/src/components/LogsTable.js +++ b/web/src/components/LogsTable.js @@ -85,8 +85,10 @@ const LogsTable = () => { return {t('管理')}; case 4: return {t('系统')}; + case 5: + return {t('错误')}; default: - return {t('未知')}; + return {t('未知')}; } } @@ -160,7 +162,7 @@ const LogsTable = () => { color={stringToColor(record.model_name)} size='large' onClick={(event) => { - copyText(event, record.model_name).then(r => {}); + copyText(event, record.model_name).then(r => { }); }} > {' '}{record.model_name}{' '} @@ -170,13 +172,13 @@ const LogsTable = () => { <> + { - copyText(event, record.model_name).then(r => {}); + copyText(event, record.model_name).then(r => { }); }} > {t('请求并计费模型')}{' '}{record.model_name}{' '} @@ -185,7 +187,7 @@ const LogsTable = () => { color={stringToColor(other.upstream_model_name)} size='large' onClick={(event) => { - copyText(event, other.upstream_model_name).then(r => {}); + copyText(event, other.upstream_model_name).then(r => { }); }} > {t('实际模型')}{' '}{other.upstream_model_name}{' '} @@ -197,9 +199,9 @@ const LogsTable = () => { color={stringToColor(record.model_name)} size='large' onClick={(event) => { - copyText(event, record.model_name).then(r => {}); + copyText(event, record.model_name).then(r => { }); }} - suffixIcon={} + suffixIcon={} > {' '}{record.model_name}{' '} @@ -298,7 +300,7 @@ const LogsTable = () => { const handleSelectAll = (checked) => { const allKeys = Object.keys(COLUMN_KEYS).map(key => COLUMN_KEYS[key]); const updatedColumns = {}; - + allKeys.forEach(key => { // For admin-only columns, only enable them if user is admin if ((key === COLUMN_KEYS.CHANNEL || key === COLUMN_KEYS.USERNAME || key === COLUMN_KEYS.RETRY) && !isAdminUser) { @@ -307,7 +309,7 @@ const LogsTable = () => { updatedColumns[key] = checked; } }); - + setVisibleColumns(updatedColumns); }; @@ -325,7 +327,7 @@ const LogsTable = () => { className: isAdmin() ? 'tableShow' : 'tableHiddle', render: (text, record, index) => { return isAdminUser ? ( - record.type === 0 || record.type === 2 ? ( + (record.type === 0 || record.type === 2 || record.type === 5) ? ( { @@ -378,7 +380,7 @@ const LogsTable = () => { title: t('令牌'), dataIndex: 'token_name', render: (text, record, index) => { - return record.type === 0 || record.type === 2 ? ( + return (record.type === 0 || record.type === 2 || record.type === 5) ? ( { title: t('分组'), dataIndex: 'group', render: (text, record, index) => { - if (record.type === 0 || record.type === 2) { - if (record.group) { + if (record.type === 0 || record.type === 2 || record.type === 5) { + if (record.group) { return ( <> {renderGroup(record.group)} > ); - } else { - let other = null; - try { - other = JSON.parse(record.other); - } catch (e) { - console.error(`Failed to parse record.other: "${record.other}".`, e); - } - if (other === null) { - return <>>; - } - if (other.group !== undefined) { - return ( - <> - {renderGroup(other.group)} - > - ); - } else { - return <>>; - } - } + } else { + let other = null; + try { + other = JSON.parse(record.other); + } catch (e) { + console.error(`Failed to parse record.other: "${record.other}".`, e); + } + if (other === null) { + return <>>; + } + if (other.group !== undefined) { + return ( + <> + {renderGroup(other.group)} + > + ); + } else { + return <>>; + } + } } else { return <>>; } @@ -447,7 +449,7 @@ const LogsTable = () => { title: t('模型'), dataIndex: 'model_name', render: (text, record, index) => { - return record.type === 0 || record.type === 2 ? ( + return (record.type === 0 || record.type === 2 || record.type === 5) ? ( <>{renderModelName(record)}> ) : ( <>> @@ -487,7 +489,7 @@ const LogsTable = () => { title: t('提示'), dataIndex: 'prompt_tokens', render: (text, record, index) => { - return record.type === 0 || record.type === 2 ? ( + return (record.type === 0 || record.type === 2 || record.type === 5) ? ( <>{ {text} }> ) : ( <>> @@ -500,7 +502,7 @@ const LogsTable = () => { dataIndex: 'completion_tokens', render: (text, record, index) => { return parseInt(text) > 0 && - (record.type === 0 || record.type === 2) ? ( + (record.type === 0 || record.type === 2 || record.type === 5) ? ( <>{ {text} }> ) : ( <>> @@ -512,7 +514,7 @@ const LogsTable = () => { title: t('花费'), dataIndex: 'quota', render: (text, record, index) => { - return record.type === 0 || record.type === 2 ? ( + return (record.type === 0 || record.type === 2 || record.type === 5) ? ( <>{renderQuota(text, 6)}> ) : ( <>> @@ -588,14 +590,14 @@ const LogsTable = () => { other.cache_ratio || 1.0, ); return ( - - {content} - + + {content} + ); }, }, @@ -638,8 +640,8 @@ const LogsTable = () => { {t('全选')} - { }}> {allColumns.map(column => { // Skip admin-only columns for non-admin users - if (!isAdminUser && (column.key === COLUMN_KEYS.CHANNEL || - column.key === COLUMN_KEYS.USERNAME || - column.key === COLUMN_KEYS.RETRY)) { + if (!isAdminUser && (column.key === COLUMN_KEYS.CHANNEL || + column.key === COLUMN_KEYS.USERNAME || + column.key === COLUMN_KEYS.RETRY)) { return null; } - + return ( { // key: '渠道重试', // value: content, // }) - } + } if (isAdminUser && (logs[i].type === 0 || logs[i].type === 2)) { expandDataLocal.push({ key: t('渠道信息'), @@ -962,7 +964,7 @@ const LogsTable = () => { const handlePageChange = (page) => { setActivePage(page); - loadLogs(page, pageSize, logType).then((r) => {}); + loadLogs(page, pageSize, logType).then((r) => { }); }; const handlePageSizeChange = async (size) => { @@ -1014,26 +1016,26 @@ const LogsTable = () => { - {t('消耗额度')}: {renderQuota(stat.quota)} - RPM: {stat.rpm} - @@ -1046,7 +1048,7 @@ const LogsTable = () => { <> - { + { styleState.isMobile ? ( { > - + { - setLogType(parseInt(value)); - loadLogs(0, pageSize, parseInt(value)); - }} + defaultValue='0' + style={{ width: 120 }} + onChange={(value) => { + setLogType(parseInt(value)); + loadLogs(0, pageSize, parseInt(value)); + }} > {t('全部')} {t('充值')} {t('消费')} {t('管理')} {t('系统')} + {t('错误')}