121 lines
4.0 KiB
TypeScript
121 lines
4.0 KiB
TypeScript
import fs from 'fs';
|
|
import path from 'path';
|
|
import { Handler } from '../../Server';
|
|
import { Error, HandlerConfig, Log } from '../../type';
|
|
|
|
const enum FileType {
|
|
Log = 'log', Error = 'error'
|
|
}
|
|
|
|
const FILE_CLOSE_COUNTDOWN = 60 * 10;
|
|
const FILE_CLOSE_CHECK_DELAY = 1;
|
|
const LOG_LEVEL_TITLE = [
|
|
null, 'LOG', 'WARN', 'ERROR'
|
|
]
|
|
|
|
export default class LogSave implements Handler {
|
|
|
|
private readonly fileDir: string = null;
|
|
|
|
private timer: NodeJS.Timer = null;
|
|
private fileCache: { [id: string]: { countdown: number, stream: fs.WriteStream } } = null;
|
|
|
|
|
|
constructor(config: HandlerConfig) {
|
|
this.fileCache = {};
|
|
if (config.path)
|
|
this.fileDir = config.path as string;
|
|
else
|
|
this.fileDir = path.join(__dirname, '..', 'log');
|
|
}
|
|
|
|
|
|
private mkdir(dir: string): void {
|
|
if (!fs.existsSync(dir)) {
|
|
let parentDir = path.dirname(dir);
|
|
this.mkdir(parentDir);
|
|
fs.mkdirSync(dir);
|
|
}
|
|
}
|
|
|
|
public onServerStart(): void {
|
|
this.timer = setInterval(() => this.checkFileCache(), FILE_CLOSE_CHECK_DELAY * 1000);
|
|
|
|
}
|
|
|
|
public onServerStop(): void {
|
|
this.timer && clearInterval(this.timer);
|
|
this.timer = null;
|
|
this.checkFileCache(true);
|
|
}
|
|
|
|
public onClientConnected(id: string): void {
|
|
}
|
|
|
|
public onClientLogMessage(id: string, logs: Log[]): void {
|
|
const fileStream = this.openFile(id, FileType.Log);
|
|
logs.forEach(log => {
|
|
const date = new Date(log.time);
|
|
const timeString = `${date.getHours()}:${('0' + date.getMinutes()).slice(-2)}:${('0' + date.getSeconds()).slice(-2)}`;
|
|
fileStream.write(`[${timeString}][${LOG_LEVEL_TITLE[log.level]}] ${log.text}\n`);
|
|
});
|
|
}
|
|
|
|
public onClientErrorMessage(id: string, error: Error): void {
|
|
const fileStream = this.openFile(id, FileType.Error);
|
|
const date = new Date(error.time);
|
|
const timeString = `${date.getHours()}:${('0' + date.getMinutes()).slice(-2)}:${('0' + date.getSeconds()).slice(-2)}`;
|
|
let errorString = `time: ${timeString}\n`;
|
|
for (const key in error) {
|
|
key !== 'time' && (errorString += `${key}: ${error[key]}\n`);
|
|
}
|
|
errorString += '\n'
|
|
fileStream.write(errorString);
|
|
}
|
|
|
|
|
|
public onClientDisconnect(id: string): void {
|
|
this.closeFile(id, FileType.Log);
|
|
this.closeFile(id, FileType.Error);
|
|
}
|
|
|
|
private getFilePath(id: string, fileType: FileType): string {
|
|
const date = new Date();
|
|
const dateString = `${date.getFullYear()}-${('0' + (date.getMonth() + 1)).slice(-2)}-${('0' + date.getDate()).slice(-2)}`;
|
|
const dirPath = path.join(this.fileDir, dateString, fileType);
|
|
this.mkdir(dirPath);
|
|
return path.join(dirPath, `${id}.txt`);
|
|
}
|
|
|
|
private openFile(id: string, fileType: FileType): fs.WriteStream {
|
|
const fileName = this.getFilePath(id, fileType);
|
|
let fileData = this.fileCache[fileName];
|
|
if (!fileData) {
|
|
const stream = fs.createWriteStream(fileName, { flags: 'a' });
|
|
fileData = this.fileCache[fileName] = { countdown: FILE_CLOSE_COUNTDOWN, stream };
|
|
}
|
|
fileData.countdown = FILE_CLOSE_COUNTDOWN;
|
|
return fileData.stream;
|
|
}
|
|
|
|
private closeFile(id: string, fileType: FileType): void {
|
|
const fileName = this.getFilePath(id, fileType);
|
|
const fileData = this.fileCache[fileName];
|
|
if (!fileData) return;
|
|
fileData.stream.close();
|
|
this.fileCache[fileName] = null;
|
|
delete this.fileCache[fileName];
|
|
}
|
|
|
|
private checkFileCache(focusClose: boolean = false): void {
|
|
for (const fileName in this.fileCache) {
|
|
const fileData = this.fileCache[fileName];
|
|
if (!fileData) continue;
|
|
if (!focusClose && fileData.countdown-- > 0) continue;
|
|
fileData.stream.close();
|
|
this.fileCache[fileName] = null;
|
|
delete this.fileCache[fileName];
|
|
}
|
|
}
|
|
|
|
} |