Skip to content

Commit 17205df

Browse files
authoredMay 29, 2020
Merge pull request #34 from SukkaW/fancy-log
refactor: completely rewrite the logger
2 parents f6ae56f + 44b0d4a commit 17205df

File tree

5 files changed

+369
-95
lines changed

5 files changed

+369
-95
lines changed
 

‎.travis.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ cache:
88
- node_modules
99

1010
node_js:
11-
- "8"
1211
- "10"
13-
- "node"
12+
- "12"
13+
- "14"
1414

1515
script:
1616
- npm run eslint

‎README.md

+5-8
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[![NPM version](https://badge.fury.io/js/hexo-log.svg)](https://www.npmjs.com/package/hexo-log)
55
[![Coverage Status](https://coveralls.io/repos/hexojs/hexo-log/badge.svg?branch=master)](https://coveralls.io/r/hexojs/hexo-log?branch=master)
66

7-
Logger for Hexo, based on [bunyan] with better console output.
7+
Logger for Hexo.
88

99
## Installation
1010

@@ -15,22 +15,19 @@ $ npm install hexo-log --save
1515
## Usage
1616

1717
``` js
18-
var log = require('hexo-log')({
18+
const log = require('hexo-log')({
1919
debug: false,
2020
silent: false
21-
});
21+
})
2222

2323
log.info('Hello world');
2424
```
2525

2626
Option | Description | Default
2727
--- | --- | ---
28-
`debug` | Display debug message and save log to `debug.log` file. | `false`
29-
`silent` | Don't display message in console. | `false`
30-
`name` | Name | `hexo`
28+
`debug` | Display debug message. | `false`
29+
`silent` | Don't display any message in console. | `false`
3130

3231
## License
3332

3433
MIT
35-
36-
[bunyan]: https://github.com/trentm/node-bunyan

‎lib/log.js

+73-55
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
'use strict';
22

3-
const bunyan = require('hexo-bunyan');
3+
const { Console } = require('console');
44
const chalk = require('chalk');
5-
const Writable = require('stream').Writable;
65

7-
const levelNames = {
6+
const TRACE = 10;
7+
const DEBUG = 20;
8+
const INFO = 30;
9+
const WARN = 40;
10+
const ERROR = 50;
11+
const FATAL = 60;
12+
13+
const LEVEL_NAMES = {
814
10: 'TRACE',
915
20: 'DEBUG',
1016
30: 'INFO ',
@@ -13,7 +19,7 @@ const levelNames = {
1319
60: 'FATAL'
1420
};
1521

16-
const levelColors = {
22+
const LEVEL_COLORS = {
1723
10: 'gray',
1824
20: 'gray',
1925
30: 'green',
@@ -22,77 +28,89 @@ const levelColors = {
2228
60: 'bgRed'
2329
};
2430

25-
function ConsoleStream(env) {
26-
Writable.call(this, {objectMode: true});
31+
const console = new Console({
32+
stdout: process.stdout,
33+
stderr: process.stderr,
34+
colorMode: false
35+
});
2736

28-
this.debug = env.debug;
29-
}
37+
class Logger {
38+
constructor(options = {}) {
39+
const silent = options.silent || false;
40+
this._debug = options.debug || false;
3041

31-
require('util').inherits(ConsoleStream, Writable);
42+
this.level = INFO;
3243

33-
ConsoleStream.prototype._write = function(data, enc, callback) {
34-
const level = data.level;
35-
let msg = '';
44+
if (silent) {
45+
this.level = FATAL + 10;
46+
}
3647

37-
// Time
38-
if (this.debug) {
39-
msg += chalk.gray(formatTime(data.time)) + ' ';
48+
if (this._debug) {
49+
this.level = TRACE;
50+
}
4051
}
4152

42-
// Level
43-
msg += chalk[levelColors[level]](levelNames[level]) + ' ';
53+
_writeLogOutput(level, consoleArgs) {
54+
if (this._debug) {
55+
const str = new Date().toISOString().substring(11, 23) + ' ';
4456

45-
// Message
46-
msg += data.msg + '\n';
57+
if (level === TRACE || level >= WARN) {
58+
process.stderr.write(chalk[LEVEL_COLORS[DEBUG]](str));
59+
} else {
60+
process.stdout.write(chalk[LEVEL_COLORS[DEBUG]](str));
61+
}
62+
}
4763

48-
// Error
49-
if (data.err) {
50-
const err = data.err.stack || data.err.message;
51-
if (err) msg += chalk.yellow(err) + '\n';
64+
if (level >= this.level) {
65+
const str = chalk[LEVEL_COLORS[level]](LEVEL_NAMES[level]) + ' ';
66+
if (level === TRACE || level >= WARN) {
67+
process.stderr.write(str);
68+
} else {
69+
process.stdout.write(str);
70+
}
71+
72+
if (level === TRACE) {
73+
console.trace(...consoleArgs);
74+
} else if (level < INFO) {
75+
console.debug(...consoleArgs);
76+
} else if (level < WARN) {
77+
console.info(...consoleArgs);
78+
} else if (level < ERROR) {
79+
console.warn(...consoleArgs);
80+
} else {
81+
console.error(...consoleArgs);
82+
}
83+
}
5284
}
5385

54-
if (level >= 40) {
55-
process.stderr.write(msg);
56-
} else {
57-
process.stdout.write(msg);
86+
trace(...args) {
87+
this._writeLogOutput(TRACE, args);
5888
}
5989

60-
callback();
61-
};
62-
63-
function formatTime(date) {
64-
return date.toISOString().substring(11, 23);
65-
}
90+
debug(...args) {
91+
this._writeLogOutput(DEBUG, args);
92+
}
6693

67-
function createLogger(options) {
68-
options = options || {};
94+
info(...args) {
95+
this._writeLogOutput(INFO, args);
96+
}
6997

70-
const streams = [];
98+
warn(...args) {
99+
this._writeLogOutput(WARN, args);
100+
}
71101

72-
if (!options.silent) {
73-
streams.push({
74-
type: 'raw',
75-
level: options.debug ? 'trace' : 'info',
76-
stream: new ConsoleStream(options)
77-
});
102+
error(...args) {
103+
this._writeLogOutput(ERROR, args);
78104
}
79105

80-
if (options.debug) {
81-
streams.push({
82-
level: 'trace',
83-
path: 'debug.log'
84-
});
106+
fatal(...args) {
107+
this._writeLogOutput(FATAL, args);
85108
}
109+
}
86110

87-
const logger = bunyan.createLogger({
88-
name: options.name || 'hexo',
89-
streams,
90-
serializers: {
91-
err: bunyan.stdSerializers.err
92-
}
93-
});
111+
function createLogger(options) {
112+
const logger = new Logger(options);
94113

95-
// Alias for logger levels
96114
logger.d = logger.debug;
97115
logger.i = logger.info;
98116
logger.w = logger.warn;

‎package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
],
3030
"license": "MIT",
3131
"dependencies": {
32-
"hexo-bunyan": "^2.0.0",
3332
"chalk": "^3.0.0"
3433
},
3534
"devDependencies": {
@@ -43,6 +42,6 @@
4342
"sinon": "^8.0.1"
4443
},
4544
"engines": {
46-
"node": ">=8.6.0"
45+
"node": ">=10.13.0"
4746
}
4847
}

‎test/index.js

+288-28
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,37 @@
11
'use strict';
22

3-
const should = require('chai').should(); // eslint-disable-line
3+
require('chai').should();
44
const rewire = require('rewire');
55
const sinon = require('sinon');
66

7+
const noop = () => {};
8+
const fakeConsole = {
9+
trace: noop,
10+
debug: noop,
11+
info: noop,
12+
warn: noop,
13+
error: noop
14+
};
15+
const fakeProcess = {
16+
process: {
17+
stderr: {
18+
write: noop
19+
},
20+
stdout: {
21+
write: noop
22+
}
23+
}
24+
};
25+
26+
const logger = require('../lib/log');
27+
728
describe('hexo-log', () => {
8-
const logger = require('../lib/log');
9-
const loggerModule = rewire('../lib/log');
29+
let loggerModule;
30+
31+
beforeEach(() => {
32+
sinon.restore();
33+
loggerModule = rewire('../lib/log');
34+
});
1035

1136
it('add alias for levels', () => {
1237
const log = logger();
@@ -18,70 +43,305 @@ describe('hexo-log', () => {
1843
log.log.should.eql(log.info);
1944
});
2045

21-
it('default name is hexo', () => {
22-
const log = logger();
46+
it('trace - should call console.trace', () => {
47+
const spy = sinon.spy();
48+
loggerModule.__set__('console.trace', spy);
49+
50+
loggerModule.__with__(fakeProcess)(() => {
51+
const log = loggerModule({ debug: true });
52+
log.trace('test');
53+
});
2354

24-
log.fields.name.should.eql('hexo');
55+
spy.called.should.be.true;
2556
});
2657

27-
it('options.name', () => {
28-
const log = logger({ name: 'foo' });
58+
it('trace - with stderr and no stdout', () => {
59+
const stdoutSpy = sinon.spy();
60+
const stderrSpy = sinon.spy();
2961

30-
log.fields.name.should.eql('foo');
31-
});
62+
loggerModule.__set__('console', fakeConsole);
3263

33-
it('level should be trace if options.debug is true', () => {
34-
const log = logger({ debug: true });
64+
loggerModule.__with__({
65+
process: {
66+
stderr: {
67+
write: stderrSpy
68+
},
69+
stdout: {
70+
write: stdoutSpy
71+
}
72+
}
73+
})(() => {
74+
const log = loggerModule({ debug: true });
75+
log.trace('test');
76+
});
3577

36-
log.streams[0].level.should.eql(10);
78+
stderrSpy.called.should.be.true;
79+
stdoutSpy.called.should.be.false;
3780
});
3881

39-
it('should add file stream if options.debug is true', () => {
40-
const log = logger({ debug: true });
82+
it('debug - should call console.debug', () => {
83+
const spy = sinon.spy();
84+
loggerModule.__set__('console.debug', spy);
85+
86+
loggerModule.__with__(fakeProcess)(() => {
87+
const log = loggerModule({ debug: true });
88+
log.debug('test');
89+
});
4190

42-
log.streams[1].path.should.eql('debug.log');
91+
spy.called.should.be.true;
4392
});
4493

45-
it('should remove console stream if options.silent is true', () => {
46-
const log = logger({ silent: true });
94+
it('debug - with stdout and no stderr', () => {
95+
const stdoutSpy = sinon.spy();
96+
const stderrSpy = sinon.spy();
4797

48-
log.streams.length.should.eql(0);
98+
loggerModule.__set__('console', fakeConsole);
99+
100+
loggerModule.__with__({
101+
process: {
102+
stderr: {
103+
write: stderrSpy
104+
},
105+
stdout: {
106+
write: stdoutSpy
107+
}
108+
}
109+
})(() => {
110+
const log = loggerModule({ debug: true });
111+
log.debug('test');
112+
});
113+
114+
stderrSpy.called.should.be.false;
115+
stdoutSpy.called.should.be.true;
49116
});
50117

51-
it('should display time if options.debug is true', () => {
118+
it('info - should call console.info', () => {
52119
const spy = sinon.spy();
53-
const now = new Date();
120+
loggerModule.__set__('console.info', spy);
121+
122+
loggerModule.__with__(fakeProcess)(() => {
123+
const log = loggerModule({ debug: true });
124+
log.info('test');
125+
});
126+
127+
spy.called.should.be.true;
128+
});
129+
130+
it('info - with stdout and no stderr', () => {
131+
const stdoutSpy = sinon.spy();
132+
const stderrSpy = sinon.spy();
133+
134+
loggerModule.__set__('console', fakeConsole);
54135

55136
loggerModule.__with__({
56137
process: {
138+
stderr: {
139+
write: stderrSpy
140+
},
57141
stdout: {
58-
write: spy
142+
write: stdoutSpy
59143
}
60144
}
61145
})(() => {
62-
sinon.useFakeTimers(now.valueOf());
63146
const log = loggerModule({ debug: true });
64147
log.info('test');
65-
sinon.restore();
66148
});
67149

68-
spy.args[0][0].should.contain(now.toISOString().substring(11, 23));
150+
stderrSpy.called.should.be.false;
151+
stdoutSpy.called.should.be.true;
152+
});
153+
154+
it('warn - should call console.warn', () => {
155+
const spy = sinon.spy();
156+
loggerModule.__set__('console.warn', spy);
157+
158+
loggerModule.__with__(fakeProcess)(() => {
159+
const log = loggerModule({ debug: true });
160+
log.warn('test');
161+
});
162+
163+
spy.called.should.be.true;
164+
});
165+
166+
it('warn - with stderr and no stdout', () => {
167+
const stdoutSpy = sinon.spy();
168+
const stderrSpy = sinon.spy();
169+
170+
loggerModule.__set__('console', fakeConsole);
171+
172+
loggerModule.__with__({
173+
process: {
174+
stderr: {
175+
write: stderrSpy
176+
},
177+
stdout: {
178+
write: stdoutSpy
179+
}
180+
}
181+
})(() => {
182+
const log = loggerModule({ debug: true });
183+
log.warn('test');
184+
});
185+
186+
stderrSpy.called.should.be.true;
187+
stdoutSpy.called.should.be.false;
188+
});
189+
190+
it('error - should call console.error', () => {
191+
const spy = sinon.spy();
192+
loggerModule.__set__('console.error', spy);
193+
194+
loggerModule.__with__(fakeProcess)(() => {
195+
const log = loggerModule({ debug: true });
196+
log.error('test');
197+
});
198+
199+
spy.called.should.be.true;
69200
});
70201

71-
it('should print error to process.stderr stream', () => {
202+
it('error - with stderr and no stdout', () => {
203+
const stdoutSpy = sinon.spy();
204+
const stderrSpy = sinon.spy();
205+
206+
loggerModule.__set__('console', fakeConsole);
207+
208+
loggerModule.__with__({
209+
process: {
210+
stderr: {
211+
write: stderrSpy
212+
},
213+
stdout: {
214+
write: stdoutSpy
215+
}
216+
}
217+
})(() => {
218+
const log = loggerModule({ debug: true });
219+
log.error('test');
220+
});
221+
222+
stderrSpy.called.should.be.true;
223+
stdoutSpy.called.should.be.false;
224+
});
225+
226+
it('fatal - should call console.error', () => {
72227
const spy = sinon.spy();
228+
loggerModule.__set__('console.error', spy);
229+
230+
loggerModule.__with__(fakeProcess)(() => {
231+
const log = loggerModule({ debug: true });
232+
log.fatal('test');
233+
});
234+
235+
spy.called.should.be.true;
236+
});
237+
238+
it('fatal - with stderr and no stdout', () => {
239+
const stdoutSpy = sinon.spy();
240+
const stderrSpy = sinon.spy();
241+
242+
loggerModule.__set__('console', fakeConsole);
73243

74244
loggerModule.__with__({
75245
process: {
76246
stderr: {
247+
write: stderrSpy
248+
},
249+
stdout: {
250+
write: stdoutSpy
251+
}
252+
}
253+
})(() => {
254+
const log = loggerModule({ debug: true });
255+
log.fatal('test');
256+
});
257+
258+
stderrSpy.called.should.be.true;
259+
stdoutSpy.called.should.be.false;
260+
});
261+
262+
it('options.debug - should display time', () => {
263+
const spy = sinon.spy();
264+
const now = new Date();
265+
266+
loggerModule.__set__('console', fakeConsole);
267+
268+
loggerModule.__with__({
269+
process: {
270+
stdout: {
77271
write: spy
78272
}
79273
}
80274
})(() => {
81-
const log = loggerModule();
275+
sinon.useFakeTimers(now.valueOf());
276+
const log = loggerModule({ debug: true });
277+
log.info('test');
278+
sinon.restore();
279+
});
280+
281+
spy.args[0][0].should.contain(now.toISOString().substring(11, 23));
282+
});
283+
284+
it('options.silent - should not display anything', () => {
285+
const consoleTraceSpy = sinon.spy();
286+
const consoleDebugSpy = sinon.spy();
287+
const consoleInfoSpy = sinon.spy();
288+
const consoleWarnSpy = sinon.spy();
289+
const consoleErrorSpy = sinon.spy();
290+
291+
loggerModule.__set__('console.trace', consoleTraceSpy);
292+
loggerModule.__set__('console.debug', consoleDebugSpy);
293+
loggerModule.__set__('console.info', consoleInfoSpy);
294+
loggerModule.__set__('console.warn', consoleWarnSpy);
295+
loggerModule.__set__('console.error', consoleErrorSpy);
296+
297+
loggerModule.__with__(fakeProcess)(() => {
298+
const log = loggerModule({ silent: true });
299+
log.trace('test');
300+
log.debug('test');
301+
log.info('test');
302+
log.warn('test');
82303
log.error('test');
304+
log.fatal('test');
83305
});
84306

85-
spy.calledOnce.should.be.true;
307+
consoleTraceSpy.called.should.be.false;
308+
consoleDebugSpy.called.should.be.false;
309+
consoleInfoSpy.called.should.be.false;
310+
consoleWarnSpy.calledOnce.should.be.false;
311+
consoleErrorSpy.calledTwice.should.be.false;
312+
});
313+
});
314+
315+
describe('hexo-log example', () => {
316+
const log = logger({ debug: true });
317+
const log2 = logger();
318+
it('log.trace()', () => {
319+
log.trace('Hello, World!');
320+
log2.trace('Hello, World!');
321+
});
322+
323+
it('log.debug()', () => {
324+
log.debug('Hello, World!');
325+
log2.debug('Hello, World!');
326+
});
327+
328+
it('log.info()', () => {
329+
log.info('Hello, World!');
330+
log2.info('Hello, World!');
331+
});
332+
333+
it('log.warn()', () => {
334+
log.warn('Hello, World!');
335+
log2.warn('Hello, World!');
336+
});
337+
338+
it('log.error()', () => {
339+
log.error('Hello, World!');
340+
log2.error('Hello, World!');
341+
});
342+
343+
it('log.fatal()', () => {
344+
log.fatal('Hello, World!');
345+
log2.fatal('Hello, World!');
86346
});
87347
});

0 commit comments

Comments
 (0)
Please sign in to comment.