Просмотр исходного кода

feat: add error logging functionality to relay and update logs table for error type display

jasonzeng 10 месяцев назад
Родитель
Сommit
97bc2b4474
3 измененных файлов с 124 добавлено и 71 удалено
  1. 20 0
      controller/relay.go
  2. 30 0
      model/log.go
  3. 74 71
      web/src/components/LogsTable.js

+ 20 - 0
controller/relay.go

@@ -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 - 0
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{}) {

+ 74 - 71
web/src/components/LogsTable.js

@@ -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>
@@ -298,7 +300,7 @@ const LogsTable = () => {
   const handleSelectAll = (checked) => {
   const handleSelectAll = (checked) => {
     const allKeys = Object.keys(COLUMN_KEYS).map(key => COLUMN_KEYS[key]);
     const allKeys = Object.keys(COLUMN_KEYS).map(key => COLUMN_KEYS[key]);
     const updatedColumns = {};
     const updatedColumns = {};
-    
+
     allKeys.forEach(key => {
     allKeys.forEach(key => {
       // For admin-only columns, only enable them if user is admin
       // 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) {
       if ((key === COLUMN_KEYS.CHANNEL || key === COLUMN_KEYS.USERNAME || key === COLUMN_KEYS.RETRY) && !isAdminUser) {
@@ -307,7 +309,7 @@ const LogsTable = () => {
         updatedColumns[key] = checked;
         updatedColumns[key] = checked;
       }
       }
     });
     });
-    
+
     setVisibleColumns(updatedColumns);
     setVisibleColumns(updatedColumns);
   };
   };
 
 
@@ -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.group) {
+        if (record.type === 0 || record.type === 2 || record.type === 5) {
+          if (record.group) {
             return (
             return (
               <>
               <>
                 {renderGroup(record.group)}
                 {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 {
         } 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
-                ellipsis={{
-                  rows: 2,
-                }}
-                style={{ maxWidth: 240 }}
-            >
-              {content}
-            </Paragraph>
+          <Paragraph
+            ellipsis={{
+              rows: 2,
+            }}
+            style={{ maxWidth: 240 }}
+          >
+            {content}
+          </Paragraph>
         );
         );
       },
       },
     },
     },
@@ -638,8 +640,8 @@ const LogsTable = () => {
             {t('全选')}
             {t('全选')}
           </Checkbox>
           </Checkbox>
         </div>
         </div>
-        <div style={{ 
-          display: 'flex', 
+        <div style={{
+          display: 'flex',
           flexWrap: 'wrap',
           flexWrap: 'wrap',
           maxHeight: '400px',
           maxHeight: '400px',
           overflowY: 'auto',
           overflowY: 'auto',
@@ -649,12 +651,12 @@ 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 || 
-                                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 null;
             }
             }
-            
+
             return (
             return (
               <div key={column.key} style={{ width: '50%', marginBottom: 16, paddingRight: 8 }}>
               <div key={column.key} style={{ width: '50%', marginBottom: 16, paddingRight: 8 }}>
                 <Checkbox
                 <Checkbox
@@ -803,7 +805,7 @@ const LogsTable = () => {
         //   key: '渠道重试',
         //   key: '渠道重试',
         //   value: content,
         //   value: content,
         // })
         // })
-      }      
+      }
       if (isAdminUser && (logs[i].type === 0 || logs[i].type === 2)) {
       if (isAdminUser && (logs[i].type === 0 || logs[i].type === 2)) {
         expandDataLocal.push({
         expandDataLocal.push({
           key: t('渠道信息'),
           key: t('渠道信息'),
@@ -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) => {
@@ -1014,26 +1016,26 @@ const LogsTable = () => {
         <Header>
         <Header>
           <Spin spinning={loadingStat}>
           <Spin spinning={loadingStat}>
             <Space>
             <Space>
-              <Tag color='blue' size='large' style={{ 
-                padding: 15, 
-                borderRadius: '8px', 
+              <Tag color='blue' size='large' style={{
+                padding: 15,
+                borderRadius: '8px',
                 fontWeight: 500,
                 fontWeight: 500,
                 boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)'
                 boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)'
               }}>
               }}>
                 {t('消耗额度')}: {renderQuota(stat.quota)}
                 {t('消耗额度')}: {renderQuota(stat.quota)}
               </Tag>
               </Tag>
-              <Tag color='pink' size='large' style={{ 
-                padding: 15, 
-                borderRadius: '8px', 
+              <Tag color='pink' size='large' style={{
+                padding: 15,
+                borderRadius: '8px',
                 fontWeight: 500,
                 fontWeight: 500,
                 boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)'
                 boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)'
               }}>
               }}>
                 RPM: {stat.rpm}
                 RPM: {stat.rpm}
               </Tag>
               </Tag>
-              <Tag color='white' size='large' style={{ 
-                padding: 15, 
-                border: 'none', 
-                boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)', 
+              <Tag color='white' size='large' style={{
+                padding: 15,
+                border: 'none',
+                boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
                 borderRadius: '8px',
                 borderRadius: '8px',
                 fontWeight: 500,
                 fontWeight: 500,
               }}>
               }}>
@@ -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'
-              style={{ width: 120 }}
-              onChange={(value) => {
-                setLogType(parseInt(value));
-                loadLogs(0, pageSize, parseInt(value));
-              }}
+            defaultValue='0'
+            style={{ width: 120 }}
+            onChange={(value) => {
+              setLogType(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'