feat: 集成阿里云日志服务(SLS)

- 添加 aliyun-log SDK 依赖
- 新增 sls-logger.js 模块,支持批量日志上报、静默降级
- 在四个 API 处理函数中集成请求日志记录
- 更新 .env.example 添加 SLS 配置示例
This commit is contained in:
empty
2025-12-27 03:08:01 +08:00
parent dec2f26b5c
commit eb1096ce54
5 changed files with 379 additions and 32 deletions

149
sls-logger.js Normal file
View File

@@ -0,0 +1,149 @@
/**
* 阿里云日志服务SLS日志模块
*
* 功能:
* - 将 API 请求/响应日志上报到阿里云 SLS
* - 批量上报,减少 API 调用
* - 环境变量缺失时静默降级
*/
import aliyunLog from 'aliyun-log';
const { Client, PutLogsRequest, LogItem, LogContent } = aliyunLog;
// 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 Client({
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 logItems = logsToSend.map(log => {
const contents = Object.entries(log).map(([key, value]) => {
return new LogContent({
key,
value: String(value ?? '')
});
});
return new LogItem({
time: Math.floor(Date.now() / 1000),
contents
});
});
const request = new PutLogsRequest({
projectName: SLS_CONFIG.project,
logStoreName: SLS_CONFIG.logstore,
logGroup: {
logs: logItems
}
});
await client.putLogs(request);
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
};