@percy/logger 1.0.0-beta.7 → 1.0.0-beta.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -27
- package/dist/browser.js +33 -0
- package/dist/bundle.js +481 -0
- package/dist/index.js +29 -0
- package/dist/logger.js +341 -0
- package/dist/util.js +37 -0
- package/package.json +24 -14
- package/test/client.js +215 -0
- package/test/helpers.js +121 -0
- package/index.js +0 -99
- package/test/.eslintrc +0 -2
- package/test/helper.js +0 -52
- package/test/index.test.js +0 -134
package/test/helpers.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const logger = require('@percy/logger');
|
|
2
|
+
const { ANSI_REG } = require('@percy/logger/dist/util');
|
|
3
|
+
const { Logger } = logger;
|
|
4
|
+
|
|
5
|
+
const ELAPSED_REG = /\s\S*?\(\d+ms\)\S*/;
|
|
6
|
+
const NEWLINE_REG = /\r\n/g;
|
|
7
|
+
const LASTLINE_REG = /\n$/;
|
|
8
|
+
|
|
9
|
+
function sanitizeLog(str, { ansi, elapsed } = {}) {
|
|
10
|
+
// normalize line endings
|
|
11
|
+
str = str.replace(NEWLINE_REG, '\n');
|
|
12
|
+
// strip ansi colors
|
|
13
|
+
if (!ansi) str = str.replace(ANSI_REG, '');
|
|
14
|
+
// strip elapsed time
|
|
15
|
+
if (!elapsed) str = str.replace(ELAPSED_REG, '');
|
|
16
|
+
// strip trailing line endings
|
|
17
|
+
return str.replace(LASTLINE_REG, '');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function TestIO(data, options) {
|
|
21
|
+
if (!process.env.__PERCY_BROWSERIFIED__) {
|
|
22
|
+
let { Writable } = require('stream');
|
|
23
|
+
|
|
24
|
+
return Object.assign(new Writable(), {
|
|
25
|
+
isTTY: options && options.isTTY,
|
|
26
|
+
cursorTo() {},
|
|
27
|
+
clearLine() {},
|
|
28
|
+
|
|
29
|
+
_write(chunk, encoding, callback) {
|
|
30
|
+
data.push(sanitizeLog(chunk.toString(), options));
|
|
31
|
+
callback();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function spy(object, method, func) {
|
|
38
|
+
if (object[method].reset) {
|
|
39
|
+
object[method].reset();
|
|
40
|
+
return object[method];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let spy = Object.assign(function spy(...args) {
|
|
44
|
+
spy.calls.push(args);
|
|
45
|
+
if (func) return func.apply(this, args);
|
|
46
|
+
}, {
|
|
47
|
+
restore: () => (object[method] = spy.originalValue),
|
|
48
|
+
reset: () => (spy.calls.length = 0),
|
|
49
|
+
originalValue: object[method],
|
|
50
|
+
calls: []
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
object[method] = spy;
|
|
54
|
+
return spy;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const helpers = {
|
|
58
|
+
constructor: Logger,
|
|
59
|
+
loglevel: logger.loglevel,
|
|
60
|
+
stdout: [],
|
|
61
|
+
stderr: [],
|
|
62
|
+
|
|
63
|
+
get messages() {
|
|
64
|
+
return Logger.instance &&
|
|
65
|
+
Logger.instance.messages;
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
mock(options) {
|
|
69
|
+
helpers.reset();
|
|
70
|
+
helpers.options = options;
|
|
71
|
+
|
|
72
|
+
if (!process.env.__PERCY_BROWSERIFIED__) {
|
|
73
|
+
Logger.stdout = TestIO(helpers.stdout, options);
|
|
74
|
+
Logger.stderr = TestIO(helpers.stderr, options);
|
|
75
|
+
} else {
|
|
76
|
+
spy(Logger.prototype, 'write', function(lvl, msg) {
|
|
77
|
+
let stdio = lvl === 'info' ? 'stdout' : 'stderr';
|
|
78
|
+
helpers[stdio].push(sanitizeLog(msg, helpers.options));
|
|
79
|
+
return this.write.originalValue.call(this, lvl, msg);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
spy(console, 'log');
|
|
83
|
+
spy(console, 'warn');
|
|
84
|
+
spy(console, 'error');
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
reset(soft) {
|
|
89
|
+
if (soft) Logger.instance.loglevel('info');
|
|
90
|
+
else delete Logger.instance;
|
|
91
|
+
|
|
92
|
+
helpers.stdout.length = 0;
|
|
93
|
+
helpers.stderr.length = 0;
|
|
94
|
+
|
|
95
|
+
if (console.log.reset) {
|
|
96
|
+
console.log.reset();
|
|
97
|
+
console.warn.reset();
|
|
98
|
+
console.error.reset();
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
dump() {
|
|
103
|
+
if (!helpers.messages || !helpers.messages.size) return;
|
|
104
|
+
if (console.log.and) console.log.and.callThrough();
|
|
105
|
+
|
|
106
|
+
let write = m => process.env.__PERCY_BROWSERIFIED__
|
|
107
|
+
? console.log(m) : process.stderr.write(`${m}\n`);
|
|
108
|
+
let logs = Array.from(helpers.messages);
|
|
109
|
+
|
|
110
|
+
logger.loglevel('debug');
|
|
111
|
+
|
|
112
|
+
write(logger.format('testing', 'warn', '--- DUMPING LOGS ---'));
|
|
113
|
+
|
|
114
|
+
logs.reduce((lastlog, { debug, level, message, timestamp }) => {
|
|
115
|
+
write(logger.format(debug, level, message, timestamp - lastlog));
|
|
116
|
+
return timestamp;
|
|
117
|
+
}, logs[0].timestamp);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
module.exports = helpers;
|
package/index.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
const os = require('os');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const { createLogger, transports, format } = require('winston');
|
|
4
|
-
const colors = require('colors/safe');
|
|
5
|
-
|
|
6
|
-
// A very basic url regexp only used for highlighting URLs blue in the CLI.
|
|
7
|
-
const URL_REGEXP = /\b(https?:\/\/)[\w-]+(\.[\w-]+)*(:\d+)?(\/\S*)?\b/gi;
|
|
8
|
-
const TEMP_DIR = path.join(os.tmpdir(), 'percy');
|
|
9
|
-
|
|
10
|
-
// Custom logging formatter to color log levels, insert a megenta label, and
|
|
11
|
-
// highlight URLs logged to the transport
|
|
12
|
-
function formatter({ label, level, message }) {
|
|
13
|
-
label = colors.magenta(label);
|
|
14
|
-
|
|
15
|
-
if (level === 'error') {
|
|
16
|
-
message = colors.red(message);
|
|
17
|
-
} else if (level === 'warn') {
|
|
18
|
-
message = colors.yellow(message);
|
|
19
|
-
} else if (level === 'info') {
|
|
20
|
-
// highlight urls blue
|
|
21
|
-
message = message.replace(URL_REGEXP, colors.blue('$&'));
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return `[${label}] ${message}`;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Global logger
|
|
28
|
-
const logger = createLogger({
|
|
29
|
-
transports: [
|
|
30
|
-
// console transport logs errors by default
|
|
31
|
-
new transports.Console({
|
|
32
|
-
level: 'error',
|
|
33
|
-
stderrLevels: ['error'],
|
|
34
|
-
format: format.combine(
|
|
35
|
-
format.label({ label: 'percy' }),
|
|
36
|
-
format.printf(formatter)
|
|
37
|
-
)
|
|
38
|
-
}),
|
|
39
|
-
// file transport logs everything
|
|
40
|
-
new transports.File({
|
|
41
|
-
level: 'debug',
|
|
42
|
-
filename: path.join(TEMP_DIR, `percy.${Date.now()}.log`),
|
|
43
|
-
format: format.combine(
|
|
44
|
-
format.timestamp(),
|
|
45
|
-
format.json()
|
|
46
|
-
)
|
|
47
|
-
})
|
|
48
|
-
]
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
// Attach the formatter so external logs can utilize it
|
|
52
|
-
logger.formatter = message => formatter(
|
|
53
|
-
typeof message === 'string' ? { label: 'percy', message } : message
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
// Method to change the global logger's console log level. The second argument
|
|
57
|
-
// can be used to set the appropriate level based on boolean flags and fallback
|
|
58
|
-
// to the provided log level.
|
|
59
|
-
logger.loglevel = function loglevel(level, flags = {}) {
|
|
60
|
-
if (!level) return this.transports[0].level;
|
|
61
|
-
|
|
62
|
-
if (flags.verbose) level = 'debug';
|
|
63
|
-
else if (flags.quiet) level = 'warn';
|
|
64
|
-
else if (flags.silent) level = 'silent';
|
|
65
|
-
|
|
66
|
-
this.transports[0].level = level;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
// Patch the error method to handle error objects. Winston accepts objects with
|
|
70
|
-
// messages and meta as an argument but will fail to log real error instances.
|
|
71
|
-
logger.error = function(message) {
|
|
72
|
-
// libraries may also throw errors which are not technically Error instances
|
|
73
|
-
if (typeof message === 'object') {
|
|
74
|
-
// get the stack trace for debug (no ternary to always fallback to string)
|
|
75
|
-
message = (this.loglevel() === 'debug' && message.stack) || message.toString();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// return super.error(message)
|
|
79
|
-
return this.constructor.prototype.error.call(this, message);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
// Patch the query method to return a promise, query the file transport only,
|
|
83
|
-
// and allow filtering logs with a filter function.
|
|
84
|
-
logger.query = function(options = {}) {
|
|
85
|
-
return new Promise((resolve, reject) => {
|
|
86
|
-
// the query method mutates options to normalize and set defaults, so the
|
|
87
|
-
// same is done here to set more desirable defaults
|
|
88
|
-
options.limit = options.limit || Infinity;
|
|
89
|
-
options.order = options.order || 'asc';
|
|
90
|
-
|
|
91
|
-
this.transports[1].query(options, (err, logs) => {
|
|
92
|
-
if (err) return reject(err);
|
|
93
|
-
if (options.filter) logs = logs.filter(options.filter);
|
|
94
|
-
resolve(logs);
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
module.exports = logger;
|
package/test/.eslintrc
DELETED
package/test/helper.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import stripAnsi from 'strip-ansi';
|
|
2
|
-
|
|
3
|
-
const og = {
|
|
4
|
-
out: process.stdout.write,
|
|
5
|
-
err: process.stderr.write
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
function format(chunk, { ansi = false } = {}) {
|
|
9
|
-
// strip ansi and normalize line endings
|
|
10
|
-
return (ansi ? chunk : stripAnsi(chunk)).replace('\r\n', '\n');
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function tryFinally(fn, cb) {
|
|
14
|
-
let done = (r, e) => {
|
|
15
|
-
if ((cb(), e)) throw e;
|
|
16
|
-
return r;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
let r, e;
|
|
20
|
-
try { r = fn(); } catch (err) { e = err; }
|
|
21
|
-
|
|
22
|
-
if (typeof r?.then === 'function') {
|
|
23
|
-
return r.then(done, e => done(null, e));
|
|
24
|
-
} else {
|
|
25
|
-
return done(r, e);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const stdio = {
|
|
30
|
-
1: [],
|
|
31
|
-
2: [],
|
|
32
|
-
|
|
33
|
-
capture(fn, options) {
|
|
34
|
-
stdio.flush();
|
|
35
|
-
process.stdout.write = chunk => stdio[1].push(format(chunk, options));
|
|
36
|
-
process.stderr.write = chunk => stdio[2].push(format(chunk, options));
|
|
37
|
-
return fn ? tryFinally(fn, stdio.restore) : null;
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
restore() {
|
|
41
|
-
process.stdout.write = og.out;
|
|
42
|
-
process.stderr.write = og.err;
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
flush() {
|
|
46
|
-
let output = [null, stdio[1], stdio[2]];
|
|
47
|
-
stdio[1] = []; stdio[2] = [];
|
|
48
|
-
return output;
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
export default stdio;
|
package/test/index.test.js
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import expect from 'expect';
|
|
4
|
-
import colors from 'colors/safe';
|
|
5
|
-
import stdio from './helper';
|
|
6
|
-
import log from '..';
|
|
7
|
-
|
|
8
|
-
describe('logger', () => {
|
|
9
|
-
const label = colors.magenta('percy');
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
let { dirname, filename } = log.transports[1];
|
|
13
|
-
let logfile = path.join(dirname, filename);
|
|
14
|
-
fs.writeFileSync(logfile, '');
|
|
15
|
-
log.loglevel('debug');
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
afterEach(() => {
|
|
19
|
-
log.loglevel('error');
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('formats the message with a percy label', () => {
|
|
23
|
-
stdio.capture(() => log.debug('test'), { ansi: true });
|
|
24
|
-
expect(stdio[1]).toEqual([`[${label}] test\n`]);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('formats errors red', () => {
|
|
28
|
-
stdio.capture(() => log.error('error'), { ansi: true });
|
|
29
|
-
expect(stdio[2]).toEqual([`[${label}] ${colors.red('error')}\n`]);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('formats warnings yellow', () => {
|
|
33
|
-
stdio.capture(() => log.warn('warning'), { ansi: true });
|
|
34
|
-
expect(stdio[1]).toEqual([`[${label}] ${colors.yellow('warning')}\n`]);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('formats info URLs blue', () => {
|
|
38
|
-
let url = 'https://localhost:3000/foobar/baz.png';
|
|
39
|
-
stdio.capture(() => log.info(`url = ${url}`), { ansi: true });
|
|
40
|
-
expect(stdio[1]).toEqual([`[${label}] url = ${colors.blue(url)}\n`]);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe('#formatter()', () => {
|
|
44
|
-
it('returns a string formatted as a percy log', () => {
|
|
45
|
-
expect(log.formatter('testing')).toEqual(`[${label}] testing`);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('accepts a logger meta object', () => {
|
|
49
|
-
expect(log.formatter({ label: 'test', message: 'foobar' }))
|
|
50
|
-
.toEqual(`[${colors.magenta('test')}] foobar`);
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
describe('#loglevel()', () => {
|
|
55
|
-
it('sets the first transport log level', () => {
|
|
56
|
-
expect(log.transports[0].level).toBe('debug');
|
|
57
|
-
log.loglevel('info');
|
|
58
|
-
expect(log.transports[0].level).toBe('info');
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('returns the first transport log level without args', () => {
|
|
62
|
-
expect(log.loglevel()).toBe(log.transports[0].level);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('sets the log level to debug with a verbose flag', () => {
|
|
66
|
-
log.loglevel('info', { verbose: true });
|
|
67
|
-
expect(log.loglevel()).toBe('debug');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('sets the log level to warn with a quiet flag', () => {
|
|
71
|
-
log.loglevel('info', { quiet: true });
|
|
72
|
-
expect(log.loglevel()).toBe('warn');
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('sets the log level to silent with a silent flag', () => {
|
|
76
|
-
log.loglevel('info', { silent: true });
|
|
77
|
-
expect(log.loglevel()).toBe('silent');
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
describe('#error()', () => {
|
|
82
|
-
it('is patched to log error instance strings', () => {
|
|
83
|
-
log.loglevel('error');
|
|
84
|
-
stdio.capture(() => log.error(new Error('message')));
|
|
85
|
-
expect(stdio[2]).toEqual(['[percy] Error: message\n']);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('logs the error instance stack trace in debug', () => {
|
|
89
|
-
let err = new Error('message');
|
|
90
|
-
stdio.capture(() => log.error(err));
|
|
91
|
-
expect(stdio[2]).toEqual([`[percy] ${err.stack}\n`]);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('falls back if there is no error instance stack in debug', () => {
|
|
95
|
-
let err = { toString: () => 'error' };
|
|
96
|
-
stdio.capture(() => log.error(err));
|
|
97
|
-
expect(stdio[2]).toEqual(['[percy] error\n']);
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
describe('#query()', () => {
|
|
102
|
-
beforeEach(() => {
|
|
103
|
-
stdio.capture(() => {
|
|
104
|
-
log.info('foo', { foo: true });
|
|
105
|
-
log.info('bar', { bar: true });
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('is promisified', async () => {
|
|
110
|
-
await expect(log.query()).resolves.toEqual([
|
|
111
|
-
expect.objectContaining({ message: 'foo', foo: true }),
|
|
112
|
-
expect.objectContaining({ message: 'bar', bar: true })
|
|
113
|
-
]);
|
|
114
|
-
|
|
115
|
-
await expect(log.query({
|
|
116
|
-
get level() { throw new Error('test'); }
|
|
117
|
-
})).rejects.toThrow('test');
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('can filter logs', async () => {
|
|
121
|
-
await expect(log.query({
|
|
122
|
-
filter: log => log.foo
|
|
123
|
-
})).resolves.toEqual([
|
|
124
|
-
expect.objectContaining({ message: 'foo', foo: true })
|
|
125
|
-
]);
|
|
126
|
-
|
|
127
|
-
await expect(log.query({
|
|
128
|
-
filter: log => log.bar
|
|
129
|
-
})).resolves.toEqual([
|
|
130
|
-
expect.objectContaining({ message: 'bar', bar: true })
|
|
131
|
-
]);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
});
|