@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 CHANGED
@@ -1,58 +1,93 @@
1
1
  # @percy/logger
2
2
 
3
- Common [winston](https://github.com/winstonjs/winston) logger used throughout the Percy CLI.
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 log from '@percy/logger'
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
- ### `#loglevel([level][, flags])`
50
+ ### `logger.loglevel([level][, flags])`
17
51
 
18
- Sets or retrieves the log level of the console transport. If the second argument is provided,
19
- `level` is treated as a fallback when all logging flags are `false`. When no arguments are provided,
20
- the method will return the current log level of the console transport.
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
- log.loglevel('info', { verbose: true })
24
- log.loglevel() === 'debug'
57
+ logger.loglevel('info', { verbose: true })
58
+ logger.loglevel() === 'debug'
25
59
 
26
- log.loglevel('info', { quiet: true })
27
- log.loglevel() === 'warn'
60
+ logger.loglevel('info', { quiet: true })
61
+ logger.loglevel() === 'warn'
28
62
 
29
- log.loglevel('info', { silent: true })
30
- log.loglevel() === 'silent'
63
+ logger.loglevel('info', { silent: true })
64
+ logget.loglevel() === 'silent'
31
65
 
32
- log.loglevel('info')
33
- log.loglevel() === 'info'
66
+ logger.loglevel('info')
67
+ logger.loglevel() === 'info'
34
68
  ```
35
69
 
36
- ### `#error(errorOrMessage)`
70
+ ### `logger.format(message, debug[, level])`
37
71
 
38
- Patched `#error()` method that handles `Error` instance's and similar error objects. When
39
- `#loglevel()` is equal to `debug`, the `Error` instance's stack trace is logged.
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
- log.loglevel('debug')
43
- log.error(new Error('example'))
44
- // [percy] Error: example
45
- // at example:2:10
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
- ### `#query(options)`
84
+ ### `logger.query(filter)`
50
85
 
51
- Patched `#query()` method that is promisified and allows a `filter` function option.
86
+ Returns an array of logs matching the provided filter function.
52
87
 
53
88
  ``` js
54
- let logs = await log.query({
55
- filter: log => true
56
- // ...other query options (see winston docs)
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-beta.9",
3
+ "version": "1.0.0",
4
4
  "license": "MIT",
5
- "main": "index.js",
6
- "scripts": {
7
- "lint": "eslint --ignore-path ../../.gitignore .",
8
- "test": "cross-env NODE_ENV=test mocha",
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
- "mocha": {
15
- "require": "../../scripts/babel-register"
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
- "dependencies": {
18
- "colors": "^1.4.0",
19
- "winston": "^3.2.1"
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
- "devDependencies": {
22
- "strip-ansi": "^6.0.0"
42
+ "rollup": {
43
+ "output": {
44
+ "name": "PercyLogger"
45
+ }
23
46
  },
24
- "gitHead": "57a2eeb90c7f5cdf8827c78be1e5c12df581f4b5"
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
@@ -1,2 +0,0 @@
1
- env:
2
- mocha: true
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;
@@ -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
- });