forked from datawhale/whale-town-end
feat:添加日志功能
This commit is contained in:
@@ -61,6 +61,15 @@ export class LogManagementService {
|
||||
this.maxSize = this.configService.get('LOG_MAX_SIZE', '10m');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日志目录的绝对路径
|
||||
*
|
||||
* 说明:用于后台打包下载 logs/ 整目录。
|
||||
*/
|
||||
getLogDirAbsolutePath(): string {
|
||||
return path.resolve(this.logDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* 定期清理过期日志文件
|
||||
*
|
||||
@@ -307,6 +316,67 @@ export class LogManagementService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取运行日志尾部(用于后台查看)
|
||||
*
|
||||
* 说明:
|
||||
* - 开发环境默认读取 dev.log
|
||||
* - 生产环境默认读取 app.log(可选 access/error)
|
||||
* - 通过读取文件尾部一定字节数实现“近似 tail”,避免大文件全量读取
|
||||
*/
|
||||
async getRuntimeLogTail(options?: {
|
||||
type?: 'app' | 'access' | 'error' | 'dev';
|
||||
lines?: number;
|
||||
}): Promise<{
|
||||
file: string;
|
||||
updated_at: string;
|
||||
lines: string[];
|
||||
}> {
|
||||
const isProduction = this.configService.get('NODE_ENV') === 'production';
|
||||
const requestedLines = Math.max(1, Math.min(Number(options?.lines ?? 200), 2000));
|
||||
const requestedType = options?.type;
|
||||
|
||||
const allowedFiles = isProduction
|
||||
? {
|
||||
app: 'app.log',
|
||||
access: 'access.log',
|
||||
error: 'error.log',
|
||||
}
|
||||
: {
|
||||
dev: 'dev.log',
|
||||
};
|
||||
|
||||
const defaultType = isProduction ? 'app' : 'dev';
|
||||
const typeKey = (requestedType && requestedType in allowedFiles ? requestedType : defaultType) as keyof typeof allowedFiles;
|
||||
const fileName = allowedFiles[typeKey];
|
||||
const filePath = path.join(this.logDir, fileName);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return { file: fileName, updated_at: new Date().toISOString(), lines: [] };
|
||||
}
|
||||
|
||||
const stats = fs.statSync(filePath);
|
||||
const maxBytes = 256 * 1024; // 256KB 足够覆盖常见的数百行日志
|
||||
const readBytes = Math.min(stats.size, maxBytes);
|
||||
const startPos = Math.max(0, stats.size - readBytes);
|
||||
|
||||
const fd = fs.openSync(filePath, 'r');
|
||||
try {
|
||||
const buffer = Buffer.alloc(readBytes);
|
||||
fs.readSync(fd, buffer, 0, readBytes, startPos);
|
||||
const text = buffer.toString('utf8');
|
||||
const allLines = text.split(/\r?\n/).filter((l) => l.length > 0);
|
||||
const tailLines = allLines.slice(-requestedLines);
|
||||
return {
|
||||
file: fileName,
|
||||
updated_at: stats.mtime.toISOString(),
|
||||
lines: tailLines,
|
||||
};
|
||||
} finally {
|
||||
fs.closeSync(fd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析最大文件数配置
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user