Files
upage-git/app/lib/.server/logger.server.ts
LIlGG c31e366af9 feat: add support for DouBao, Ernie, Kimi, Qwen, ZhiPu LLM providers
Introduces new provider modules for DouBao, Ernie, Kimi, Qwen, and ZhiPu, and registers them in the LLM registry. Updates documentation and .env.example to include configuration instructions for these providers. Refactors OpenAI provider to support OpenAI-compatible endpoints. Adds @ai-sdk/openai-compatible and node-fetch dependencies.
2025-09-29 16:28:56 +08:00

176 lines
4.8 KiB
TypeScript

import { Chalk } from 'chalk';
import * as fs from 'fs';
import * as path from 'path';
import * as winston from 'winston';
import DailyRotateFile from 'winston-daily-rotate-file';
import type { DebugLevel } from '~/utils/logger';
const chalk = new Chalk({ level: 3 });
type LoggerFunction = (...messages: any[]) => void;
interface Logger {
trace: LoggerFunction;
debug: LoggerFunction;
info: LoggerFunction;
warn: LoggerFunction;
error: LoggerFunction;
setLevel: (level: DebugLevel) => void;
}
let currentLevel: DebugLevel =
(process.env.LOG_LEVEL as DebugLevel | undefined) || (import.meta.env.DEV ? 'debug' : 'info');
// 文件日志配置
const enableFileLogging = process.env.USAGE_LOG_FILE !== 'false';
const logDir = path.join(process.cwd(), 'logs');
// 确保日志目录存在
if (enableFileLogging) {
try {
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
} catch (error) {
console.error('Failed to create logs directory:', error);
}
}
// 创建 Winston logger 实例
const winstonLogger = enableFileLogging
? winston.createLogger({
level: currentLevel,
format: winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.printf((info) => {
const { timestamp, level, message, scope } = info;
return `${timestamp} [${level.toUpperCase()}]${scope ? ` [${scope}]` : ''}: ${message}`;
}),
),
transports: [
// 按日期分割的错误日志文件
new DailyRotateFile({
filename: path.join(logDir, 'error-%DATE%.log'),
datePattern: 'YYYY-MM-DD',
level: 'error',
maxSize: '10m', // 10MB
maxFiles: 14, // 保留14天
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
}) as winston.transport,
// 所有级别日志
new DailyRotateFile({
filename: path.join(logDir, 'combined-%DATE%.log'),
datePattern: 'YYYY-MM-DD',
maxSize: '20m', // 20MB
maxFiles: 7, // 保留7天
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
}) as winston.transport,
],
})
: null;
export const logger: Logger = {
trace: (...messages: any[]) => log('trace', undefined, messages),
debug: (...messages: any[]) => log('debug', undefined, messages),
info: (...messages: any[]) => log('info', undefined, messages),
warn: (...messages: any[]) => log('warn', undefined, messages),
error: (...messages: any[]) => log('error', undefined, messages),
setLevel,
};
export function createScopedLogger(scope: string): Logger {
return {
trace: (...messages: any[]) => log('trace', scope, messages),
debug: (...messages: any[]) => log('debug', scope, messages),
info: (...messages: any[]) => log('info', scope, messages),
warn: (...messages: any[]) => log('warn', scope, messages),
error: (...messages: any[]) => log('error', scope, messages),
setLevel,
};
}
function setLevel(level: DebugLevel) {
if ((level === 'trace' || level === 'debug') && import.meta.env.PROD) {
return;
}
currentLevel = level;
// 更新 Winston logger 级别
if (winstonLogger) {
winstonLogger.level = level;
}
}
function log(level: DebugLevel, scope: string | undefined, messages: any[]) {
const levelOrder: DebugLevel[] = ['trace', 'debug', 'info', 'warn', 'error'];
if (levelOrder.indexOf(level) < levelOrder.indexOf(currentLevel)) {
return;
}
const allMessages = messages.reduce((acc, current) => {
if (acc.endsWith('\n')) {
return acc + current;
}
if (!acc) {
return current;
}
return `${acc} ${current}`;
}, '');
const labelBackgroundColor = getColorForLevel(level);
const labelTextColor = level === 'warn' ? '#000000' : '#FFFFFF';
let labelText = formatText(` ${level.toUpperCase()} `, labelTextColor, labelBackgroundColor);
if (scope) {
labelText = `${labelText} ${formatText(` ${scope} `, '#FFFFFF', '77828D')}`;
}
// 控制台日志
console.log(`${labelText}`, allMessages);
// 写入文件日志
if (winstonLogger) {
try {
winstonLogger.log({
level,
message: allMessages,
scope,
});
} catch (error) {
console.error('Failed to write to log file:', error);
}
}
}
function formatText(text: string, color: string, bg: string) {
return chalk.bgHex(bg)(chalk.hex(color)(text));
}
function getColorForLevel(level: DebugLevel): string {
switch (level) {
case 'trace':
case 'debug': {
return '#77828D';
}
case 'info': {
return '#1389FD';
}
case 'warn': {
return '#FFDB6C';
}
case 'error': {
return '#EE4744';
}
default: {
return '#000000';
}
}
}
export const renderLogger = createScopedLogger('Render');