mirror of
https://github.com/whyour/qinglong.git
synced 2026-07-01 04:40:38 +08:00
系统设置增加系统运行日志
This commit is contained in:
@@ -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);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -29,4 +29,5 @@ export default async ({ expressApp }: { expressApp: Application }) => {
|
||||
expressApp.use(Sentry.Handlers.tracingHandler());
|
||||
|
||||
Logger.info('✌️ Sentry loaded');
|
||||
console.log('✌️ Sentry loaded');
|
||||
};
|
||||
|
||||
@@ -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
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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}`,
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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: %s,cron: %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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user