feat: 集成阿里云日志服务(SLS)
- 添加 aliyun-log SDK 依赖 - 新增 sls-logger.js 模块,支持批量日志上报、静默降级 - 在四个 API 处理函数中集成请求日志记录 - 更新 .env.example 添加 SLS 配置示例
This commit is contained in:
149
sls-logger.js
Normal file
149
sls-logger.js
Normal 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
|
||||
};
|
||||
Reference in New Issue
Block a user