@percy/logger 1.0.0-beta.9 → 1.0.0
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/package.json +37 -14
- package/index.js +0 -101
- package/test/.eslintrc +0 -2
- package/test/helper.js +0 -52
- package/test/index.test.js +0 -142
package/README.md
CHANGED
|
@@ -1,58 +1,93 @@
|
|
|
1
1
|
# @percy/logger
|
|
2
2
|
|
|
3
|
-
Common
|
|
3
|
+
Common logger used throughout the Percy CLI and SDKs.
|
|
4
|
+
|
|
5
|
+
- [Usage](#usage)
|
|
6
|
+
- [`logger()`](#loggerdebug)
|
|
7
|
+
- [`logger.loglevel()`](#loggerloglevel)
|
|
8
|
+
- [`logger.format()`](#loggerformat)
|
|
9
|
+
- [`logger.query()`](#loggerquery)
|
|
4
10
|
|
|
5
11
|
## Usage
|
|
6
12
|
|
|
7
13
|
``` js
|
|
8
|
-
import
|
|
14
|
+
import logger from '@percy/logger'
|
|
15
|
+
|
|
16
|
+
const log = logger('foobar')
|
|
9
17
|
|
|
10
18
|
log.info('info message')
|
|
11
19
|
log.error('error message')
|
|
12
20
|
log.warn('warning message')
|
|
13
21
|
log.debug('debug message')
|
|
22
|
+
log.deprecated('deprecation message')
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### `logger([debug])`
|
|
26
|
+
|
|
27
|
+
Creates a group of logging functions that will be associated with the provided `debug` label. When
|
|
28
|
+
debug logging is enabled, this label is printed with the `[percy:*]` label and can be filtered via
|
|
29
|
+
the `PERCY_DEBUG` environment variable.
|
|
30
|
+
|
|
31
|
+
``` js
|
|
32
|
+
PERCY_DEBUG="one:*,*:a,-*:b"
|
|
33
|
+
|
|
34
|
+
logger.loglevel('debug')
|
|
35
|
+
|
|
36
|
+
logger('one').debug('test')
|
|
37
|
+
logger('one:a').debug('test')
|
|
38
|
+
logger('one:b').debug('test')
|
|
39
|
+
logger('one:c').debug('test')
|
|
40
|
+
logger('two').debug('test')
|
|
41
|
+
logger('two:a').debug('test')
|
|
42
|
+
|
|
43
|
+
// only logs from the matching debug string are printed
|
|
44
|
+
//=> [percy:one] test
|
|
45
|
+
//=> [percy:one:a] test
|
|
46
|
+
//=> [percy:one:c] test
|
|
47
|
+
//=> [percy:two:a] test
|
|
14
48
|
```
|
|
15
49
|
|
|
16
|
-
###
|
|
50
|
+
### `logger.loglevel([level][, flags])`
|
|
17
51
|
|
|
18
|
-
Sets or retrieves the log level of the
|
|
19
|
-
|
|
20
|
-
|
|
52
|
+
Sets or retrieves the log level of the shared logger. If the second argument is provided, `level` is
|
|
53
|
+
treated as a fallback when all logging flags are `false`. When no arguments are provided, the method
|
|
54
|
+
will return the current log level of the shared logger.
|
|
21
55
|
|
|
22
56
|
``` js
|
|
23
|
-
|
|
24
|
-
|
|
57
|
+
logger.loglevel('info', { verbose: true })
|
|
58
|
+
logger.loglevel() === 'debug'
|
|
25
59
|
|
|
26
|
-
|
|
27
|
-
|
|
60
|
+
logger.loglevel('info', { quiet: true })
|
|
61
|
+
logger.loglevel() === 'warn'
|
|
28
62
|
|
|
29
|
-
|
|
30
|
-
|
|
63
|
+
logger.loglevel('info', { silent: true })
|
|
64
|
+
logget.loglevel() === 'silent'
|
|
31
65
|
|
|
32
|
-
|
|
33
|
-
|
|
66
|
+
logger.loglevel('info')
|
|
67
|
+
logger.loglevel() === 'info'
|
|
34
68
|
```
|
|
35
69
|
|
|
36
|
-
###
|
|
70
|
+
### `logger.format(message, debug[, level])`
|
|
37
71
|
|
|
38
|
-
|
|
39
|
-
|
|
72
|
+
Returns a formatted `message` depending on the provided level and logger's own log level. When
|
|
73
|
+
debugging, the `debug` label is added to the prepended `[percy:*]` label.
|
|
40
74
|
|
|
41
75
|
``` js
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
76
|
+
logger.format('foobar', 'test')
|
|
77
|
+
//=> [percy] foobar
|
|
78
|
+
|
|
79
|
+
logger.loglevel('debug')
|
|
80
|
+
logger.format('foobar', 'test', warn')
|
|
81
|
+
//=> [percy:test] foobar (yellow for warnings)
|
|
47
82
|
```
|
|
48
83
|
|
|
49
|
-
###
|
|
84
|
+
### `logger.query(filter)`
|
|
50
85
|
|
|
51
|
-
|
|
86
|
+
Returns an array of logs matching the provided filter function.
|
|
52
87
|
|
|
53
88
|
``` js
|
|
54
|
-
let logs =
|
|
55
|
-
|
|
56
|
-
|
|
89
|
+
let logs = logger.query(log => {
|
|
90
|
+
return log.level === 'debug' &&
|
|
91
|
+
log.message.match(/foobar/)
|
|
57
92
|
})
|
|
58
93
|
```
|
package/package.json
CHANGED
|
@@ -1,25 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@percy/logger",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"test:coverage": "nyc yarn test"
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/percy/cli",
|
|
8
|
+
"directory": "packages/logger"
|
|
10
9
|
},
|
|
11
10
|
"publishConfig": {
|
|
12
11
|
"access": "public"
|
|
13
12
|
},
|
|
14
|
-
"
|
|
15
|
-
"
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=14"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"./dist",
|
|
18
|
+
"./test/helpers.js",
|
|
19
|
+
"./test/client.js"
|
|
20
|
+
],
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"browser": "./dist/bundle.js",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": "./dist/index.js",
|
|
26
|
+
"./utils": "./dist/utils.js",
|
|
27
|
+
"./test/helpers": "./test/helpers.js",
|
|
28
|
+
"./test/client": "./test/client.js"
|
|
16
29
|
},
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
|
|
30
|
+
"imports": {
|
|
31
|
+
"#logger": {
|
|
32
|
+
"node": "./dist/logger.js",
|
|
33
|
+
"default": "./dist/browser.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "node ../../scripts/build",
|
|
38
|
+
"lint": "eslint --ignore-path ../../.gitignore .",
|
|
39
|
+
"test": "node ../../scripts/test",
|
|
40
|
+
"test:coverage": "yarn test --coverage"
|
|
20
41
|
},
|
|
21
|
-
"
|
|
22
|
-
"
|
|
42
|
+
"rollup": {
|
|
43
|
+
"output": {
|
|
44
|
+
"name": "PercyLogger"
|
|
45
|
+
}
|
|
23
46
|
},
|
|
24
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "6df509421a60144e4f9f5d59dc57a5675372a0b2"
|
|
25
48
|
}
|
package/index.js
DELETED
|
@@ -1,101 +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: process.env.PERCY_LOGLEVEL || 'info',
|
|
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 and debug methods to handle error objects. Winston accepts objects
|
|
70
|
-
// with messages and meta as an argument but will fail to log real error instances.
|
|
71
|
-
for (let method of ['error', 'debug']) {
|
|
72
|
-
logger[method] = function(message) {
|
|
73
|
-
// libraries may also throw errors which are not technically Error instances
|
|
74
|
-
if (typeof message === 'object') {
|
|
75
|
-
// get the stack trace for debug (no ternary to always fallback to string)
|
|
76
|
-
message = (this.loglevel() === 'debug' && message.stack) || message.toString();
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// return super[method](message)
|
|
80
|
-
return this.constructor.prototype[method].call(this, message);
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Patch the query method to return a promise, query the file transport only,
|
|
85
|
-
// and allow filtering logs with a filter function.
|
|
86
|
-
logger.query = function(options = {}) {
|
|
87
|
-
return new Promise((resolve, reject) => {
|
|
88
|
-
// the query method mutates options to normalize and set defaults, so the
|
|
89
|
-
// same is done here to set more desirable defaults
|
|
90
|
-
options.limit = options.limit || Infinity;
|
|
91
|
-
options.order = options.order || 'asc';
|
|
92
|
-
|
|
93
|
-
this.transports[1].query(options, (err, logs) => {
|
|
94
|
-
if (err) return reject(err);
|
|
95
|
-
if (options.filter) logs = logs.filter(options.filter);
|
|
96
|
-
resolve(logs);
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
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,142 +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('#debug()', () => {
|
|
102
|
-
it('is patched to log error instance strings', () => {
|
|
103
|
-
let err = { toString: () => 'error' };
|
|
104
|
-
stdio.capture(() => log.debug(err));
|
|
105
|
-
expect(stdio[1]).toEqual(['[percy] error\n']);
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
describe('#query()', () => {
|
|
110
|
-
beforeEach(() => {
|
|
111
|
-
stdio.capture(() => {
|
|
112
|
-
log.info('foo', { foo: true });
|
|
113
|
-
log.info('bar', { bar: true });
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('is promisified', async () => {
|
|
118
|
-
await expect(log.query()).resolves.toEqual([
|
|
119
|
-
expect.objectContaining({ message: 'foo', foo: true }),
|
|
120
|
-
expect.objectContaining({ message: 'bar', bar: true })
|
|
121
|
-
]);
|
|
122
|
-
|
|
123
|
-
await expect(log.query({
|
|
124
|
-
get level() { throw new Error('test'); }
|
|
125
|
-
})).rejects.toThrow('test');
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('can filter logs', async () => {
|
|
129
|
-
await expect(log.query({
|
|
130
|
-
filter: log => log.foo
|
|
131
|
-
})).resolves.toEqual([
|
|
132
|
-
expect.objectContaining({ message: 'foo', foo: true })
|
|
133
|
-
]);
|
|
134
|
-
|
|
135
|
-
await expect(log.query({
|
|
136
|
-
filter: log => log.bar
|
|
137
|
-
})).resolves.toEqual([
|
|
138
|
-
expect.objectContaining({ message: 'bar', bar: true })
|
|
139
|
-
]);
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
});
|