系统设置增加系统运行日志

This commit is contained in:
whyour
2023-08-21 00:10:43 +08:00
parent b002cbef3a
commit 4f7649f157
33 changed files with 864 additions and 112 deletions
+13
View File
@@ -249,4 +249,17 @@ export default (app: Router) => {
}
},
);
route.get(
'/log',
async (req: Request, res: Response, next: NextFunction) => {
try {
const systemService = Container.get(SystemService);
await systemService.getSystemLog(res);
} catch (e) {
return next(e);
}
},
);
};
+2
View File
@@ -18,10 +18,12 @@ async function startServer() {
const server = app
.listen(config.port, () => {
Logger.debug(`✌️ 后端服务启动成功!`);
console.debug(`✌️ 后端服务启动成功!`);
process.send?.('ready');
})
.on('error', (err) => {
Logger.error(err);
console.error(err);
process.exit(1);
});
+2
View File
@@ -30,6 +30,7 @@ const logPath = path.join(dataPath, 'log/');
const dbPath = path.join(dataPath, 'db/');
const uploadPath = path.join(dataPath, 'upload/');
const sshdPath = path.join(dataPath, 'ssh.d/');
const systemLogPath = path.join(dataPath, 'syslog/');
const envFile = path.join(configPath, 'env.sh');
const confFile = path.join(configPath, 'config.sh');
@@ -110,4 +111,5 @@ export default {
lastVersionFile,
sqliteFile,
sshdPath,
systemLogPath,
};
+12 -5
View File
@@ -307,22 +307,28 @@ interface IFile {
type: 'directory' | 'file';
parent: string;
mtime: number;
size?: number;
children?: IFile[];
}
export function dirSort(a: IFile, b: IFile) {
if (a.type !== b.type) return FileType[a.type] < FileType[b.type] ? -1 : 1;
else if (a.mtime !== b.mtime) return a.mtime > b.mtime ? -1 : 1;
export function dirSort(a: IFile, b: IFile): number {
if (a.type !== b.type) {
return FileType[a.type] < FileType[b.type] ? -1 : 1
}else if (a.mtime !== b.mtime) {
return a.mtime > b.mtime ? -1 : 1
} else {
return 0;
}
}
export function readDirs(
dir: string,
baseDir: string = '',
blacklist: string[] = [],
) {
): IFile[] {
const relativePath = path.relative(baseDir, dir);
const files = fs.readdirSync(dir);
const result: any = files
const result: IFile[] = files
.filter((x) => !blacklist.includes(x))
.map((file: string) => {
const subPath = path.join(dir, file);
@@ -344,6 +350,7 @@ export function readDirs(
isLeaf: true,
key,
parent: relativePath,
size: stats.size,
mtime: stats.mtime.getTime(),
};
});
+7 -2
View File
@@ -10,20 +10,25 @@ export default async ({ expressApp }: { expressApp: Application }) => {
try {
depInjectorLoader();
Logger.info('✌️ Dependency Injector loaded');
console.log('✌️ Dependency Injector loaded');
expressLoader({ app: expressApp });
Logger.info('✌️ Express loaded');
console.log('✌️ Express loaded');
await initData();
Logger.info('✌️ init data loaded');
console.log('✌️ init data loaded');
await linkDeps();
Logger.info('✌️ link deps loaded');
console.log('✌️ link deps loaded');
initTask();
Logger.info('✌️ init task loaded');
console.log('✌️ init task loaded');
} catch (error) {
Logger.info('✌️ depInjectorLoader expressLoader initData linkDeps failed');
Logger.error(error);
Logger.error(`✌️ depInjectorLoader expressLoader initData linkDeps failed, ${error}`);
console.error(`✌️ depInjectorLoader expressLoader initData linkDeps failed ${error}`);
}
};
+2 -1
View File
@@ -122,9 +122,10 @@ export default async () => {
});
}
console.log('✌️ DB loaded');
Logger.info('✌️ DB loaded');
} catch (error) {
Logger.info('✌️ DB load failed');
console.error('✌️ DB load failed');
Logger.error(error);
}
};
+2
View File
@@ -15,6 +15,7 @@ import { EnvModel } from '../data/env';
import { errors } from 'celebrate';
import { createProxyMiddleware } from 'http-proxy-middleware';
import { serveEnv } from '../config/serverEnv';
import Logger from './logger';
export default ({ app }: { app: Application }) => {
app.enable('trust proxy');
@@ -28,6 +29,7 @@ export default ({ app }: { app: Application }) => {
target: `http://localhost:${config.publicPort}/api`,
changeOrigin: true,
pathRewrite: { '/api/public': '' },
logProvider: () => Logger
}),
);
+7
View File
@@ -28,6 +28,7 @@ const TaskAfterFile = path.join(configPath, 'task_after.sh');
const homedir = os.homedir();
const sshPath = path.resolve(homedir, '.ssh');
const sshdPath = path.join(dataPath, 'ssh.d');
const systemLogPath = path.join(dataPath, 'syslog');
export default async () => {
const authFileExist = await fileExist(authConfigFile);
@@ -39,6 +40,7 @@ export default async () => {
const sshDirExist = await fileExist(sshPath);
const bakDirExist = await fileExist(bakPath);
const sshdDirExist = await fileExist(sshdPath);
const systemLogDirExist = await fileExist(systemLogPath);
const tmpDirExist = await fileExist(tmpPath);
const scriptNotifyJsFileExist = await fileExist(scriptNotifyJsFile);
const scriptNotifyPyFileExist = await fileExist(scriptNotifyPyFile);
@@ -77,6 +79,10 @@ export default async () => {
fs.mkdirSync(sshdPath);
}
if (!systemLogDirExist) {
fs.mkdirSync(systemLogPath);
}
// 初始化文件
if (!authFileExist) {
fs.writeFileSync(authConfigFile, fs.readFileSync(sampleAuthFile));
@@ -105,4 +111,5 @@ export default async () => {
dotenv.config({ path: confFile });
Logger.info('✌️ Init file down');
console.log('✌️ Init file down');
};
+27 -21
View File
@@ -1,32 +1,38 @@
import winston from 'winston';
import 'winston-daily-rotate-file';
import config from '../config';
import path from 'path';
const transports = [];
if (process.env.NODE_ENV !== 'development') {
transports.push(new winston.transports.Console());
} else {
transports.push(
new winston.transports.Console({
format: winston.format.combine(
winston.format.cli(),
winston.format.splat(),
),
}),
);
const levelMap: Record<string, string> = {
info: '🔵',
warn: '🟡',
error: '🔴',
debug: '🔶'
}
const customFormat = winston.format.combine(
winston.format.splat(),
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
winston.format.align(),
winston.format.printf((i) => `[${levelMap[i.level]}${i.level}] [${[i.timestamp]}]: ${i.message}`),
);
const defaultOptions = {
format: customFormat,
datePattern: "YYYY-MM-DD",
maxSize: "20m",
maxFiles: "7d",
};
const LoggerInstance = winston.createLogger({
level: config.logs.level,
levels: winston.config.npm.levels,
format: winston.format.combine(
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss',
}),
winston.format.errors({ stack: true }),
winston.format.splat(),
winston.format.json(),
),
transports,
transports: [
new winston.transports.DailyRotateFile({
filename: path.join(config.systemLogPath, '%DATE%.log'),
...defaultOptions,
})
],
});
export default LoggerInstance;
+1
View File
@@ -29,4 +29,5 @@ export default async ({ expressApp }: { expressApp: Application }) => {
expressApp.use(Sentry.Handlers.tracingHandler());
Logger.info('✌️ Sentry loaded');
console.log('✌️ Sentry loaded');
};
+5 -1
View File
@@ -10,9 +10,11 @@ export default async ({ server }: { server: Server }) => {
process.on('SIGINT', (singal) => {
Logger.warn(`Server need close, singal ${singal}`);
console.warn(`Server need close, singal ${singal}`);
exitTime++;
if (exitTime >= 3) {
Logger.warn('Forcing server close');
console.warn('Forcing server close');
clearTimeout(timer);
process.exit(1);
}
@@ -28,11 +30,13 @@ export default async ({ server }: { server: Server }) => {
process.on('uncaughtException', (error) => {
Logger.error('Uncaught exception:', error);
console.error('Uncaught exception:', error);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
Logger.error('Unhandled rejection:', reason);
Logger.error('Unhandled rejection:', reason, promise);
console.error('Unhandled rejection:', reason, promise);
process.exit(1);
});
};
+3 -1
View File
@@ -6,7 +6,7 @@ import { credentials } from '@grpc/grpc-js';
const app = express();
const client = new HealthClient(
`localhost:${config.cronPort}`,
`0.0.0.0:${config.cronPort}`,
credentials.createInsecure(),
);
@@ -25,9 +25,11 @@ app
await require('./loaders/db').default();
Logger.debug(`✌️ 公共服务启动成功!`);
console.debug(`✌️ 公共服务启动成功!`);
process.send?.('ready');
})
.on('error', (err) => {
Logger.error(err);
console.error(err);
process.exit(1);
});
+8 -1
View File
@@ -22,11 +22,18 @@ const addCron = (
cmdStr = `${TASK_PREFIX}${cmdStr}`;
}
Logger.info(
'[schedule][创建定时任务], 任务ID: %s, cron: %s, 执行命令: %s',
id,
schedule,
command,
);
scheduleStacks.set(
id,
nodeSchedule.scheduleJob(id, schedule, async () => {
Logger.info(
`当前时间: ${dayjs().format(
`[schedule] 时间: ${dayjs().format(
'YYYY-MM-DD HH:mm:ss',
)},运行命令: ${cmdStr}`,
);
+5
View File
@@ -1,6 +1,7 @@
import { ServerUnaryCall, sendUnaryData } from '@grpc/grpc-js';
import { DeleteCronRequest, DeleteCronResponse } from '../protos/cron';
import { scheduleStacks } from './data';
import Logger from '../loaders/logger';
const delCron = (
call: ServerUnaryCall<DeleteCronRequest, DeleteCronResponse>,
@@ -8,6 +9,10 @@ const delCron = (
) => {
for (const id of call.request.ids) {
if (scheduleStacks.has(id)) {
Logger.info(
'[schedule][取消定时任务], 任务ID: %s',
id,
);
scheduleStacks.get(id)?.cancel();
scheduleStacks.delete(id);
}
+2 -1
View File
@@ -11,7 +11,7 @@ const server = new Server();
server.addService(HealthService, { check });
server.addService(CronService, { addCron, delCron });
server.bindAsync(
`localhost:${config.cronPort}`,
`0.0.0.0:${config.cronPort}`,
ServerCredentials.createInsecure(),
(err, port) => {
if (err) {
@@ -19,6 +19,7 @@ server.bindAsync(
}
server.start();
Logger.debug(`✌️ 定时服务启动成功!`);
console.debug(`✌️ 定时服务启动成功!`);
process.send?.('ready');
},
);
+1 -2
View File
@@ -370,7 +370,7 @@ export default class CronService {
try {
await killTask(doc.pid);
} catch (error) {
this.logger.silly(error);
this.logger.error(error);
}
}
}
@@ -530,7 +530,6 @@ export default class CronService {
}
});
this.logger.silly(crontab_string);
fs.writeFileSync(config.crontabFile, crontab_string);
execSync(`crontab ${config.crontabFile}`);
+9 -9
View File
@@ -66,7 +66,7 @@ export default class ScheduleService {
cp.stderr.on('data', async (data) => {
this.logger.info(
'[执行任务失败] %s,时间:%s, 错误信息%j',
'[执行任务失败] %s, 时间: %s, 错误信息: %j',
command,
new Date().toLocaleString(),
data.toString(),
@@ -76,7 +76,7 @@ export default class ScheduleService {
cp.on('error', async (err) => {
this.logger.error(
'[创建任务失败] %s,时间:%s, 错误信息%j',
'[创建任务失败] %s, 时间: %s, 错误信息: %j',
command,
new Date().toLocaleString(),
err,
@@ -86,7 +86,7 @@ export default class ScheduleService {
cp.on('exit', async (code, signal) => {
this.logger.info(
`[任务退出] ${command} 进程id: ${cp.pid}退出码 ${code}`,
`[任务退出] ${command} 进程id: ${cp.pid}, 退出码 ${code}`,
);
});
@@ -101,7 +101,7 @@ export default class ScheduleService {
});
} catch (error) {
await this.logger.error(
'执行任务%s失败,时间:%s, 错误信息%j',
'[执行任务失败] 命令: %s, 时间: %s, 错误信息: %j',
command,
new Date().toLocaleString(),
error,
@@ -119,7 +119,7 @@ export default class ScheduleService {
) {
const _id = this.formatId(id);
this.logger.info(
'[创建cron任务]任务ID: %scron: %s任务名: %s执行命令: %s',
'[创建cron任务], 任务ID: %s, cron: %s, 任务名: %s, 执行命令: %s',
_id,
schedule,
name,
@@ -140,7 +140,7 @@ export default class ScheduleService {
async cancelCronTask({ id = 0, name }: ScheduleTaskType) {
const _id = this.formatId(id);
this.logger.info('[取消定时任务]任务名%s', name);
this.logger.info('[取消定时任务], 任务名: %s', name);
if (this.scheduleStacks.has(_id)) {
this.scheduleStacks.get(_id)?.cancel();
this.scheduleStacks.delete(_id);
@@ -155,7 +155,7 @@ export default class ScheduleService {
) {
const _id = this.formatId(id);
this.logger.info(
'[创建interval任务]任务ID: %s任务名: %s执行命令: %s',
'[创建interval任务], 任务ID: %s, 任务名: %s, 执行命令: %s',
_id,
name,
command,
@@ -167,7 +167,7 @@ export default class ScheduleService {
},
(err) => {
this.logger.error(
'执行任务%s失败,时间:%s, 错误信息%j',
'[执行任务失败] 命令: %s, 时间: %s, 错误信息: %j',
command,
new Date().toLocaleString(),
err,
@@ -190,7 +190,7 @@ export default class ScheduleService {
async cancelIntervalTask({ id = 0, name }: ScheduleTaskType) {
const _id = this.formatId(id);
this.logger.info('[取消interval任务]任务ID: %s任务名%s', _id, name);
this.logger.info('[取消interval任务], 任务ID: %s, 任务名: %s', _id, name);
this.intervalSchedule.removeById(_id);
}
+1 -1
View File
@@ -301,7 +301,7 @@ export default class SubscriptionService {
try {
await killTask(doc.pid);
} catch (error) {
this.logger.silly(error);
this.logger.error(error);
}
}
const absolutePath = await handleLogPath(doc.log_path as string);
+28
View File
@@ -21,11 +21,14 @@ import {
parseContentVersion,
parseVersion,
promiseExec,
readDirs,
} from '../config/util';
import { TASK_COMMAND } from '../config/const';
import taskLimit from '../shared/pLimit';
import tar from 'tar';
import path from 'path';
import fs from 'fs';
import { sum } from 'lodash';
@Service()
export default class SystemService {
@@ -275,4 +278,29 @@ export default class SystemService {
return { code: 400, message: error.message };
}
}
public async getSystemLog(res: Response) {
const result = readDirs(config.systemLogPath, config.systemLogPath);
const logs = result.reverse().filter((x) => x.title.endsWith('.log'));
res.set({
'Content-Length': sum(logs.map((x) => x.size)),
});
(function sendFiles(res, fileNames) {
if (fileNames.length === 0) {
res.end();
return;
}
const currentLog = fileNames.shift();
if (currentLog) {
const currentFileStream = fs.createReadStream(
path.join(config.systemLogPath, currentLog.title),
);
currentFileStream.on('end', () => {
sendFiles(res, fileNames);
});
currentFileStream.pipe(res, { end: false });
}
})(res, logs);
}
}
+4 -4
View File
@@ -5,13 +5,13 @@ import Logger from '../loaders/logger';
export function runCron(cmd: string): Promise<number> {
return taskLimit.runWithCpuLimit(() => {
return new Promise(async (resolve: any) => {
Logger.silly('运行命令: ' + cmd);
Logger.info(`[schedule][开始执行任务] 运行命令: ${cmd}`);
const cp = spawn(cmd, { shell: '/bin/bash' });
cp.stderr.on('data', (data) => {
Logger.info(
'[执行任务失败] %s,时间:%s, 错误信息%j',
'[schedule][执行任务失败] %s, 时间: %s, 错误信息: %j',
cmd,
new Date().toLocaleString(),
data.toString(),
@@ -19,7 +19,7 @@ export function runCron(cmd: string): Promise<number> {
});
cp.on('error', (err) => {
Logger.error(
'[创建任务失败] %s,时间:%s, 错误信息%j',
'[schedule][创建任务失败] %s, 时间: %s, 错误信息: %j',
cmd,
new Date().toLocaleString(),
err,
@@ -27,7 +27,7 @@ export function runCron(cmd: string): Promise<number> {
});
cp.on('close', async (code) => {
Logger.info(`[任务退出] ${cmd} 进程id: ${cp.pid} 退出退出码 ${code}`);
Logger.info(`[schedule][任务退出] ${cmd} 进程id: ${cp.pid} 退出, 退出码 ${code}`);
resolve();
});
});