1 | import bunyan from 'bunyan';
|
2 | import chalk from 'chalk';
|
3 | import { Writable } from 'stream';
|
4 | import path from 'path';
|
5 | import {spawn} from "child_process";
|
6 | import {LOG_REPORT_BEAT_GAP, FEFLOW_ROOT, HEART_BEAT_COLLECTION_LOG, LOG_FILE} from '../../shared/constant';
|
7 | import DBInstance from '../../shared/db';
|
8 | import osenv from "osenv";
|
9 |
|
10 | let heartDB: DBInstance;
|
11 | const reportLog = path.join(__dirname, './report');
|
12 | const pkg = require('../../../package.json');
|
13 | const PLUGE_NAME = 'feflow-' + pkg.name.split('/').pop();
|
14 | const process = require('process');
|
15 | const { debug, silent } = process.env;
|
16 | const root = path.join(osenv.home(), FEFLOW_ROOT);
|
17 | const logReportDbKey = 'log_report_beat_time';
|
18 | let logger:any;
|
19 | interface IObject {
|
20 | [key: string]: string;
|
21 | }
|
22 |
|
23 | interface Args {
|
24 | debug: Boolean;
|
25 | }
|
26 |
|
27 | type LogLevelString = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
28 | type LogLevel = LogLevelString | number;
|
29 |
|
30 | interface 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 |
|
42 | const levelNames: IObject = {
|
43 | 10: 'Trace',
|
44 | 20: 'Debug',
|
45 | 30: 'Info',
|
46 | 40: 'Warn',
|
47 | 50: 'Error',
|
48 | 60: 'Fatal'
|
49 | };
|
50 |
|
51 | const levelColors: IObject = {
|
52 | 10: 'gray',
|
53 | 20: 'gray',
|
54 | 30: 'green',
|
55 | 40: 'orange',
|
56 | 50: 'red',
|
57 | 60: 'red'
|
58 | };
|
59 |
|
60 | let logReportProcess: any = null;
|
61 | let hasCreateHeart: boolean = false;
|
62 |
|
63 | class 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,
|
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 |
|
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 |
|
145 | export 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 | }
|