- 添加 SLS 日志上报模块(sls-logger.js) - 支持批量上报(每10条或5秒间隔) - 环境变量缺失时静默降级 - 自动重试失败的日志 - 新增日志信息提取器(log-extractor.js) - 提取 Token 使用统计(input_tokens, output_tokens) - 提取用户标识信息(user_id, session_id, ip) - 提取请求参数(temperature, max_tokens, stream) - 提取消息摘要(message_count, role_distribution, tool_names) - 增强所有 API 端点的日志记录 - /v1/chat/completions - /v1/responses - /v1/messages - /v1/messages/count_tokens - 修复日志字段序列化问题 - 扁平化嵌套对象字段,避免 [object Object] - 数组字段转换为逗号分隔字符串 - 添加阿里云环境变量配置到 docker-compose.yml - ALIYUN_ACCESS_KEY_ID - ALIYUN_ACCESS_KEY_SECRET - ALIYUN_SLS_ENDPOINT - ALIYUN_SLS_PROJECT - ALIYUN_SLS_LOGSTORE - 修改认证配置为自动刷新 Token 机制 - 使用 DROID_REFRESH_KEY 替代固定的 FACTORY_API_KEY - 实现每6小时自动刷新(Token有效期8小时) - Token 持久化到 auth.json
135 lines
3.5 KiB
JavaScript
135 lines
3.5 KiB
JavaScript
/**
|
||
* 阿里云日志服务(SLS)日志模块
|
||
*
|
||
* 功能:
|
||
* - 将 API 请求/响应日志上报到阿里云 SLS
|
||
* - 批量上报,减少 API 调用
|
||
* - 环境变量缺失时静默降级
|
||
*/
|
||
|
||
import ALSClient from 'aliyun-log';
|
||
|
||
// SLS 配置
|
||
const SLS_CONFIG = {
|
||
accessKeyId: process.env.ALIYUN_ACCESS_KEY_ID,
|
||
accessKeySecret: process.env.ALIYUN_ACCESS_KEY_SECRET,
|
||
endpoint: process.env.ALIYUN_SLS_ENDPOINT,
|
||
project: process.env.ALIYUN_SLS_PROJECT,
|
||
logstore: process.env.ALIYUN_SLS_LOGSTORE
|
||
};
|
||
|
||
// 检查配置是否完整
|
||
const isConfigured = Object.values(SLS_CONFIG).every(v => v);
|
||
|
||
let client = null;
|
||
let logQueue = [];
|
||
const BATCH_SIZE = 10;
|
||
const FLUSH_INTERVAL_MS = 5000;
|
||
|
||
// 初始化 SLS Client
|
||
function initClient() {
|
||
if (!isConfigured) {
|
||
console.warn('[SLS] 阿里云日志服务未配置,日志将仅输出到控制台');
|
||
return null;
|
||
}
|
||
|
||
try {
|
||
client = new ALSClient({
|
||
accessKeyId: SLS_CONFIG.accessKeyId,
|
||
accessKeySecret: SLS_CONFIG.accessKeySecret,
|
||
endpoint: SLS_CONFIG.endpoint
|
||
});
|
||
console.log('[SLS] 阿里云日志服务客户端初始化成功');
|
||
return client;
|
||
} catch (error) {
|
||
console.error('[SLS] 初始化失败:', error.message);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 刷新日志队列
|
||
async function flushLogs() {
|
||
if (!client || logQueue.length === 0) return;
|
||
|
||
const logsToSend = logQueue.splice(0, BATCH_SIZE);
|
||
|
||
try {
|
||
const logs = logsToSend.map(log => ({
|
||
timestamp: Math.floor(Date.now() / 1000),
|
||
content: Object.fromEntries(
|
||
Object.entries(log).map(([key, value]) => [key, String(value ?? '')])
|
||
)
|
||
}));
|
||
|
||
await client.postLogStoreLogs(SLS_CONFIG.project, SLS_CONFIG.logstore, { logs });
|
||
console.log(`[SLS] 成功上报 ${logsToSend.length} 条日志`);
|
||
} catch (error) {
|
||
console.error('[SLS] 日志上报失败:', error.message);
|
||
// 失败的日志重新入队(可选:限制重试次数)
|
||
logQueue.unshift(...logsToSend);
|
||
}
|
||
}
|
||
|
||
// 定时刷新
|
||
let flushTimer = null;
|
||
function startFlushTimer() {
|
||
if (flushTimer || !isConfigured) return;
|
||
flushTimer = setInterval(flushLogs, FLUSH_INTERVAL_MS);
|
||
}
|
||
|
||
/**
|
||
* 记录 API 请求日志
|
||
* @param {Object} logData - 日志数据
|
||
* @param {string} logData.method - HTTP 方法
|
||
* @param {string} logData.endpoint - 请求路径
|
||
* @param {string} logData.model - 模型 ID
|
||
* @param {number} logData.status - 响应状态码
|
||
* @param {number} logData.duration_ms - 请求耗时
|
||
* @param {number} [logData.input_tokens] - 输入 Token 数
|
||
* @param {number} [logData.output_tokens] - 输出 Token 数
|
||
* @param {string} [logData.error] - 错误信息
|
||
*/
|
||
export function logRequest(logData) {
|
||
const enrichedLog = {
|
||
timestamp: new Date().toISOString(),
|
||
...logData
|
||
};
|
||
|
||
// 始终输出到控制台
|
||
console.log('[SLS]', JSON.stringify(enrichedLog));
|
||
|
||
if (!isConfigured) return;
|
||
|
||
logQueue.push(enrichedLog);
|
||
|
||
// 队列满时立即刷新
|
||
if (logQueue.length >= BATCH_SIZE) {
|
||
flushLogs();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 优雅关闭,刷新剩余日志
|
||
*/
|
||
export async function shutdown() {
|
||
if (flushTimer) {
|
||
clearInterval(flushTimer);
|
||
flushTimer = null;
|
||
}
|
||
await flushLogs();
|
||
console.log('[SLS] 已关闭');
|
||
}
|
||
|
||
// 初始化
|
||
initClient();
|
||
startFlushTimer();
|
||
|
||
// 进程退出时优雅关闭
|
||
process.on('SIGTERM', shutdown);
|
||
process.on('SIGINT', shutdown);
|
||
|
||
export default {
|
||
logRequest,
|
||
shutdown
|
||
};
|