Files
droid2api/sls-logger.js
empty eb1096ce54 feat: 集成阿里云日志服务(SLS)
- 添加 aliyun-log SDK 依赖
- 新增 sls-logger.js 模块,支持批量日志上报、静默降级
- 在四个 API 处理函数中集成请求日志记录
- 更新 .env.example 添加 SLS 配置示例
2025-12-27 03:08:01 +08:00

150 lines
3.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 阿里云日志服务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
};