UNPKG

4.57 kBPlain TextView Raw
1import bunyan from 'bunyan';
2import chalk from 'chalk';
3import { Writable } from 'stream';
4import path from 'path';
5import {spawn} from "child_process";
6import {LOG_REPORT_BEAT_GAP, FEFLOW_ROOT, HEART_BEAT_COLLECTION_LOG, LOG_FILE} from '../../shared/constant';
7import DBInstance from '../../shared/db';
8import osenv from "osenv";
9
10let heartDB: DBInstance;
11const reportLog = path.join(__dirname, './report');
12const pkg = require('../../../package.json');
13const PLUGE_NAME = 'feflow-' + pkg.name.split('/').pop();
14const process = require('process');
15const { debug, silent } = process.env;
16const root = path.join(osenv.home(), FEFLOW_ROOT);
17const logReportDbKey = 'log_report_beat_time';
18let logger:any;
19interface IObject {
20 [key: string]: string;
21}
22
23interface Args {
24 debug: Boolean;
25}
26
27type LogLevelString = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
28type LogLevel = LogLevelString | number;
29
30interface Stream {
31 type?: string;
32 level?: LogLevel;
33 path?: string;
34 stream?: NodeJS.WritableStream | Stream;
35 closeOnExit?: boolean;
36 period?: string;
37 count?: number;
38 name?: string;
39 reemitErrorEvents?: boolean;
40}
41
42const levelNames: IObject = {
43 10: 'Trace',
44 20: 'Debug',
45 30: 'Info',
46 40: 'Warn',
47 50: 'Error',
48 60: 'Fatal'
49};
50
51const levelColors: IObject = {
52 10: 'gray',
53 20: 'gray',
54 30: 'green',
55 40: 'orange',
56 50: 'red',
57 60: 'red'
58};
59
60let logReportProcess: any = null;
61let hasCreateHeart: boolean = false;
62
63class ConsoleStream extends Writable {
64 private debug: Boolean;
65
66 constructor(args: Args) {
67 super({
68 objectMode: true
69 });
70 this.debug = Boolean(args.debug);
71 }
72
73 private report() {
74 // 子进程执行日志上报
75 logReportProcess = spawn(process.argv[0], [reportLog], {
76 detached: true, // 使子进程在父进程退出后继续运行
77 stdio: 'ignore', // 保持后台运行
78 env: {
79 ...process.env, // env 无法把 ctx 传进去,会自动 string 化
80 debug,
81 silent,
82 },
83 windowsHide: true
84 });
85 // 父进程不会等待子进程
86 logReportProcess.unref();
87 }
88
89 async _write(data: any, enc: any, callback: any) {
90 const level = data.level;
91 const loggerName = data.name || (logger.name && logger.name.split('/').pop()) || PLUGE_NAME;
92 let msg = '';
93 if (this.debug) {
94 msg += chalk.gray(data.time) + ' ';
95 }
96 msg += chalk.keyword(levelColors[level])(`[ Feflow ${levelNames[level]} ]`);
97 msg += `[ ${loggerName} ] `;
98 msg += data.msg + '\n';
99 if (data.err) {
100 const err = data.err.stack || data.err.message;
101 if (err) msg += chalk.yellow(err) + '\n';
102 }
103
104 Object.assign(data, {
105 level: level,
106 msg: `[Feflow ${levelNames[level]}][${loggerName}]${data.msg}`,
107 date: new Date().getTime(),
108 name: loggerName
109 });
110 if (level >= 40) {
111 process.stderr.write(msg);
112 } else {
113 process.stdout.write(msg);
114 }
115
116 let cacheValidate: boolean = false;
117 const nowTime = new Date().getTime();
118 const heartDBFile = path.join(root, HEART_BEAT_COLLECTION_LOG);
119 if (!heartDB) {
120 heartDB = new DBInstance(heartDBFile);
121 }
122 const logDbData = await heartDB.read(logReportDbKey);
123 if (logDbData) {
124 const lastBeatTime = parseInt(logDbData['value'], 10);
125 // 在一次心跳时间内只允许创建一次子进程
126 cacheValidate = nowTime - lastBeatTime <= LOG_REPORT_BEAT_GAP;
127 // 防止瞬时大量数据,导致lastBeatTime还未更新时触发多次子进程创建
128 if (!cacheValidate && !hasCreateHeart) {
129 hasCreateHeart = true;
130 await heartDB.update(logReportDbKey, String(nowTime));
131 hasCreateHeart = false;
132 this.report();
133 }
134 } else if (!hasCreateHeart) {
135 hasCreateHeart = true;
136 await heartDB.create(logReportDbKey, String(nowTime));
137 hasCreateHeart = false;
138 this.report();
139 }
140 callback();
141 }
142}
143
144
145export default function createLogger(options: any) {
146 options = options || {};
147 const streams: Array<Stream> = [];
148
149 streams.push({
150 path: path.join(root, LOG_FILE),
151 });
152 if (!options.silent) {
153 streams.push({
154 type: 'raw',
155 level: options.debug ? 'trace' : 'info',
156 stream: new ConsoleStream(options),
157 });
158 }
159
160 if (options.debug) {
161 streams.push({
162 level: 'trace',
163 path: path.join(root, 'debug.log')
164 });
165 }
166
167 logger = bunyan.createLogger({
168 name: options.name || 'feflow-cli',
169 streams: streams,
170 serializers: {
171 err: bunyan.stdSerializers.err,
172 }
173 });
174 return logger;
175}