@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/dist/logger.js ADDED
@@ -0,0 +1,341 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = exports.PercyLogger = void 0;
7
+
8
+ var _util = require("./util");
9
+
10
+ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
11
+
12
+ const URL_REGEXP = /\bhttps?:\/\/[^\s/$.?#].[^\s]*\b/i;
13
+ const LOG_LEVELS = {
14
+ debug: 0,
15
+ info: 1,
16
+ warn: 2,
17
+ error: 3
18
+ }; // A PercyLogger instance retains logs in-memory for quick lookups while also writing log
19
+ // messages to stdout and stderr depending on the log level and debug string.
20
+
21
+ class PercyLogger {
22
+ // default log level
23
+ // namespace regular expressions used to determine which debug logs to write
24
+ // in-memory store for logs and meta info
25
+ // track deprecations to limit noisy logging
26
+ // static vars can be overriden for testing
27
+ // Handles setting env var values and returns a singleton
28
+ constructor() {
29
+ _defineProperty(this, "level", 'info');
30
+
31
+ _defineProperty(this, "namespaces", {
32
+ include: [/^.*?$/],
33
+ exclude: []
34
+ });
35
+
36
+ _defineProperty(this, "messages", new Set());
37
+
38
+ _defineProperty(this, "deprecations", new Set());
39
+
40
+ let {
41
+ instance = this
42
+ } = this.constructor;
43
+
44
+ if (process.env.PERCY_DEBUG) {
45
+ instance.debug(process.env.PERCY_DEBUG);
46
+ } else if (process.env.PERCY_LOGLEVEL) {
47
+ instance.loglevel(process.env.PERCY_LOGLEVEL);
48
+ }
49
+
50
+ this.constructor.instance = instance;
51
+ return instance;
52
+ } // Change log level at any time or return the current log level
53
+
54
+
55
+ loglevel(level) {
56
+ if (level) this.level = level;
57
+ return this.level;
58
+ } // Change namespaces by generating an array of namespace regular expressions from a
59
+ // comma separated debug string
60
+
61
+
62
+ debug(namespaces) {
63
+ if (this.namespaces.string === namespaces) return;
64
+ this.namespaces.string = namespaces;
65
+ namespaces = namespaces.split(/[\s,]+/).filter(Boolean);
66
+ if (!namespaces.length) return this.namespaces;
67
+ this.loglevel('debug');
68
+ this.namespaces = namespaces.reduce((namespaces, ns) => {
69
+ ns = ns.replace(/:?\*/g, m => m[0] === ':' ? ':?.*?' : '.*?');
70
+
71
+ if (ns[0] === '-') {
72
+ namespaces.exclude.push(new RegExp('^' + ns.substr(1) + '$'));
73
+ } else {
74
+ namespaces.include.push(new RegExp('^' + ns + '$'));
75
+ }
76
+
77
+ return namespaces;
78
+ }, {
79
+ string: namespaces,
80
+ include: [],
81
+ exclude: []
82
+ });
83
+ } // Creates a new log group and returns level specific functions for logging
84
+
85
+
86
+ group(name) {
87
+ return Object.keys(LOG_LEVELS).reduce((group, level) => Object.assign(group, {
88
+ [level]: this.log.bind(this, name, level)
89
+ }), {
90
+ deprecated: this.deprecated.bind(this, name),
91
+ shouldLog: this.shouldLog.bind(this, name),
92
+ progress: this.progress.bind(this, name),
93
+ format: this.format.bind(this, name),
94
+ loglevel: this.loglevel.bind(this),
95
+ stdout: this.constructor.stdout,
96
+ stderr: this.constructor.stderr
97
+ });
98
+ } // Query for a set of logs by filtering the in-memory store
99
+
100
+
101
+ query(filter) {
102
+ return Array.from(this.messages).filter(filter);
103
+ } // Formats messages before they are logged to stdio
104
+
105
+
106
+ format(debug, level, message, elapsed) {
107
+ let label = 'percy';
108
+ let suffix = '';
109
+
110
+ if (arguments.length === 1) {
111
+ // format(message)
112
+ [debug, message] = [null, debug];
113
+ } else if (arguments.length === 2) {
114
+ // format(debug, message)
115
+ [level, message] = [null, level];
116
+ }
117
+
118
+ if (this.level === 'debug') {
119
+ // include debug info in the label
120
+ if (debug) label += `:${debug}`; // include elapsed time since last log
121
+
122
+ if (elapsed != null) {
123
+ suffix = ' ' + _util.colors.grey(`(${elapsed}ms)`);
124
+ }
125
+ }
126
+
127
+ label = _util.colors.magenta(label);
128
+
129
+ if (level === 'error') {
130
+ // red errors
131
+ message = _util.colors.red(message);
132
+ } else if (level === 'warn') {
133
+ // yellow warnings
134
+ message = _util.colors.yellow(message);
135
+ } else if (level === 'info' || level === 'debug') {
136
+ // blue info and debug URLs
137
+ message = message.replace(URL_REGEXP, _util.colors.blue('$&'));
138
+ }
139
+
140
+ return `[${label}] ${message}${suffix}`;
141
+ } // Replaces the current line with a log message
142
+
143
+
144
+ progress(debug, message, persist) {
145
+ if (!this.shouldLog(debug, 'info')) return;
146
+ let {
147
+ stdout
148
+ } = this.constructor;
149
+
150
+ if (stdout.isTTY || !this._progress) {
151
+ message && (message = this.format(debug, message));
152
+ if (stdout.isTTY) stdout.cursorTo(0);else message && (message = message + '\n');
153
+ if (message) stdout.write(message);
154
+ if (stdout.isTTY) stdout.clearLine(1);
155
+ }
156
+
157
+ this._progress = !!message && {
158
+ message,
159
+ persist
160
+ };
161
+ } // Returns true or false if the level and debug group can write messages to stdio
162
+
163
+
164
+ shouldLog(debug, level) {
165
+ return LOG_LEVELS[level] != null && LOG_LEVELS[level] >= LOG_LEVELS[this.level] && !this.namespaces.exclude.some(ns => ns.test(debug)) && this.namespaces.include.some(ns => ns.test(debug));
166
+ } // Ensures that deprecation messages are not logged more than once
167
+
168
+
169
+ deprecated(debug, message, meta) {
170
+ if (this.deprecations.has(message)) return;
171
+ this.deprecations.add(message);
172
+ this.log(debug, 'warn', `Warning: ${message}`, meta);
173
+ } // Returns true if a socket is present and ready
174
+
175
+
176
+ get isRemote() {
177
+ var _this$socket;
178
+
179
+ return ((_this$socket = this.socket) === null || _this$socket === void 0 ? void 0 : _this$socket.readyState) === 1;
180
+ } // Generic log method accepts a debug group, log level, log message, and optional meta
181
+ // information to store with the message and other info
182
+
183
+
184
+ log(debug, level, message, meta = {}) {
185
+ // message might be an error object
186
+ let isError = typeof message !== 'string' && (level === 'error' || level === 'debug');
187
+ let error = isError && message; // if remote, send logs there
188
+
189
+ if (this.isRemote) {
190
+ // serialize error messages
191
+ message = isError && 'stack' in error ? {
192
+ message: error.message,
193
+ stack: error.stack
194
+ } : message;
195
+ return this.socket.send(JSON.stringify({
196
+ log: [debug, level, message, {
197
+ remote: true,
198
+ ...meta
199
+ }]
200
+ }));
201
+ } // ensure the message is a string
202
+
203
+
204
+ message = isError && message.stack || message.message || message.toString(); // timestamp each log
205
+
206
+ let timestamp = Date.now();
207
+ let entry = {
208
+ debug,
209
+ level,
210
+ message,
211
+ meta,
212
+ timestamp
213
+ };
214
+ this.messages.add(entry); // maybe write the message to stdio
215
+
216
+ if (this.shouldLog(debug, level)) {
217
+ let elapsed = timestamp - (this.lastlog || timestamp);
218
+ if (isError && this.level !== 'debug') message = error.toString();
219
+ this.write(level, this.format(debug, error ? 'error' : level, message, elapsed));
220
+ this.lastlog = timestamp;
221
+ }
222
+ } // Writes a message to stdio based on the loglevel
223
+
224
+
225
+ write(level, message) {
226
+ var _this$_progress;
227
+
228
+ let {
229
+ stdout,
230
+ stderr
231
+ } = this.constructor;
232
+ let progress = stdout.isTTY && this._progress;
233
+
234
+ if (progress) {
235
+ stdout.cursorTo(0);
236
+ stdout.clearLine();
237
+ }
238
+
239
+ (level === 'info' ? stdout : stderr).write(message + '\n');
240
+ if (!((_this$_progress = this._progress) !== null && _this$_progress !== void 0 && _this$_progress.persist)) delete this._progress;else if (progress) stdout.write(progress.message);
241
+ } // Opens a socket logging connection
242
+
243
+
244
+ connect(socket) {
245
+ // send logging environment info
246
+ let PERCY_DEBUG = process.env.PERCY_DEBUG;
247
+ let PERCY_LOGLEVEL = process.env.PERCY_LOGLEVEL || this.loglevel();
248
+ socket.send(JSON.stringify({
249
+ env: {
250
+ PERCY_DEBUG,
251
+ PERCY_LOGLEVEL
252
+ }
253
+ })); // attach remote logging handler
254
+
255
+ socket.onmessage = ({
256
+ data
257
+ }) => {
258
+ let {
259
+ log,
260
+ logAll
261
+ } = JSON.parse(data);
262
+ if (logAll) logAll.forEach(e => this.messages.add(e));
263
+ if (log) this.log(...log);
264
+ }; // return a cleanup function
265
+
266
+
267
+ return () => {
268
+ socket.onmessage = null;
269
+ };
270
+ } // Connects to a remote logger
271
+
272
+
273
+ async remote(createSocket, timeout = 1000) {
274
+ if (this.isRemote) return; // if not already connected, wait until the timeout
275
+
276
+ let err = await new Promise(resolve => {
277
+ let done = event => {
278
+ if (timeoutid == null) return;
279
+ timeoutid = clearTimeout(timeoutid);
280
+ if (this.socket) this.socket.onopen = this.socket.onerror = null;
281
+ resolve((event === null || event === void 0 ? void 0 : event.error) || (event === null || event === void 0 ? void 0 : event.type) === 'error' && 'Error: Socket connection failed');
282
+ };
283
+
284
+ let timeoutid = setTimeout(done, timeout, {
285
+ error: 'Error: Socket connection timed out'
286
+ });
287
+ Promise.resolve().then(async () => {
288
+ this.socket = await createSocket();
289
+ if (this.isRemote) return done();
290
+ this.socket.onopen = this.socket.onerror = done;
291
+ }).catch(error => done({
292
+ error
293
+ }));
294
+ }); // there was an error connecting, will fallback to normal logging
295
+
296
+ if (err) {
297
+ this.log('logger', 'debug', 'Unable to connect to remote logger');
298
+ this.log('logger', 'debug', err);
299
+ return;
300
+ } // send any messages already logged in this environment
301
+
302
+
303
+ if (this.messages.size) {
304
+ this.socket.send(JSON.stringify({
305
+ logAll: Array.from(this.messages).map(entry => ({ ...entry,
306
+ meta: {
307
+ remote: true,
308
+ ...entry.meta
309
+ }
310
+ }))
311
+ }));
312
+ } // attach an incoming message handler
313
+
314
+
315
+ this.socket.onmessage = ({
316
+ data
317
+ }) => {
318
+ let {
319
+ env
320
+ } = JSON.parse(data); // update local environment info
321
+
322
+ if (env) Object.assign(process.env, env);
323
+ }; // return a cleanup function
324
+
325
+
326
+ return () => {
327
+ this.socket.onmessage = null;
328
+ this.socket = null;
329
+ };
330
+ }
331
+
332
+ }
333
+
334
+ exports.PercyLogger = PercyLogger;
335
+
336
+ _defineProperty(PercyLogger, "stdout", process.stdout);
337
+
338
+ _defineProperty(PercyLogger, "stderr", process.stderr);
339
+
340
+ var _default = PercyLogger;
341
+ exports.default = _default;
package/dist/util.js ADDED
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.colors = exports.ANSI_REG = exports.ANSI_COLORS = void 0;
7
+ const {
8
+ assign,
9
+ entries
10
+ } = Object; // matches ansi escape sequences
11
+
12
+ const ANSI_REG = new RegExp('[\\u001B\\u009B][[\\]()#;?]*((?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)' + '|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))', 'g'); // color names by ansi escape code
13
+
14
+ exports.ANSI_REG = ANSI_REG;
15
+ const ANSI_COLORS = {
16
+ '91m': 'red',
17
+ '32m': 'green',
18
+ '93m': 'yellow',
19
+ '34m': 'blue',
20
+ '95m': 'magenta',
21
+ '90m': 'grey'
22
+ }; // colorize each line of a string using an ansi escape sequence
23
+
24
+ exports.ANSI_COLORS = ANSI_COLORS;
25
+ const LINE_REG = /^.*$/gm;
26
+
27
+ function colorize(code, str) {
28
+ return str.replace(LINE_REG, line => `\u001b[${code}${line}\u001b[39m`);
29
+ } // map ansi colors to bound colorize functions
30
+
31
+
32
+ const colors = entries(ANSI_COLORS).reduce((colors, [code, name]) => {
33
+ return assign(colors, {
34
+ [name]: colorize.bind(null, code)
35
+ });
36
+ }, {});
37
+ exports.colors = colors;
package/package.json CHANGED
@@ -1,25 +1,35 @@
1
1
  {
2
2
  "name": "@percy/logger",
3
- "version": "1.0.0-beta.7",
3
+ "version": "1.0.0-beta.73",
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
+ "main": "dist/index.js",
14
+ "browser": "dist/bundle.js",
15
+ "files": [
16
+ "dist",
17
+ "test/helpers.js",
18
+ "test/client.js"
19
+ ],
20
+ "engines": {
21
+ "node": ">=12"
16
22
  },
17
- "dependencies": {
18
- "colors": "^1.4.0",
19
- "winston": "^3.2.1"
23
+ "scripts": {
24
+ "build": "node ../../scripts/build",
25
+ "lint": "eslint --ignore-path ../../.gitignore .",
26
+ "test": "node ../../scripts/test",
27
+ "test:coverage": "yarn test --coverage"
20
28
  },
21
- "devDependencies": {
22
- "strip-ansi": "^6.0.0"
29
+ "rollup": {
30
+ "output": {
31
+ "name": "PercyLogger"
32
+ }
23
33
  },
24
- "gitHead": "5be796ec8f17958e93ada0b634899b945c9b0d60"
34
+ "gitHead": "aa8160e02bea3e04ab1d3605762f89fbe79605d4"
25
35
  }
package/test/client.js ADDED
@@ -0,0 +1,215 @@
1
+ (function() {
2
+ this.PercyLogger = this.PercyLogger || {};
3
+ this.PercyLogger.TestHelpers = (function (require$$0, require$$2) {
4
+ 'use strict';
5
+
6
+ const process = (typeof globalThis !== "undefined" && globalThis.process) || {};
7
+ process.env = process.env || {};
8
+ process.env.__PERCY_BROWSERIFIED__ = true;
9
+
10
+ globalThis.process = globalThis.process || process;
11
+
12
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
13
+
14
+ var require$$0__default = /*#__PURE__*/_interopDefaultLegacy(require$$0);
15
+ var require$$2__default = /*#__PURE__*/_interopDefaultLegacy(require$$2);
16
+
17
+ function getAugmentedNamespace(n) {
18
+ if (n.__esModule) return n;
19
+ var a = Object.defineProperty({}, '__esModule', {value: true});
20
+ Object.keys(n).forEach(function (k) {
21
+ var d = Object.getOwnPropertyDescriptor(n, k);
22
+ Object.defineProperty(a, k, d.get ? d : {
23
+ enumerable: true,
24
+ get: function () {
25
+ return n[k];
26
+ }
27
+ });
28
+ });
29
+ return a;
30
+ }
31
+
32
+ const {
33
+ assign,
34
+ entries
35
+ } = Object; // matches ansi escape sequences
36
+
37
+ const ANSI_REG$1 = new RegExp('[\\u001B\\u009B][[\\]()#;?]*((?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)' + '|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))', 'g'); // color names by ansi escape code
38
+
39
+ const ANSI_COLORS = {
40
+ '91m': 'red',
41
+ '32m': 'green',
42
+ '93m': 'yellow',
43
+ '34m': 'blue',
44
+ '95m': 'magenta',
45
+ '90m': 'grey'
46
+ }; // colorize each line of a string using an ansi escape sequence
47
+
48
+ const LINE_REG = /^.*$/gm;
49
+
50
+ function colorize(code, str) {
51
+ return str.replace(LINE_REG, line => `\u001b[${code}${line}\u001b[39m`);
52
+ } // map ansi colors to bound colorize functions
53
+
54
+
55
+ const colors = entries(ANSI_COLORS).reduce((colors, _ref) => {
56
+ let [code, name] = _ref;
57
+ return assign(colors, {
58
+ [name]: colorize.bind(null, code)
59
+ });
60
+ }, {});
61
+
62
+ var util = /*#__PURE__*/Object.freeze({
63
+ __proto__: null,
64
+ ANSI_REG: ANSI_REG$1,
65
+ ANSI_COLORS: ANSI_COLORS,
66
+ colors: colors
67
+ });
68
+
69
+ var require$$1 = /*@__PURE__*/getAugmentedNamespace(util);
70
+
71
+ const logger = require$$0__default["default"];
72
+ const {
73
+ ANSI_REG
74
+ } = require$$1;
75
+ const {
76
+ Logger
77
+ } = logger;
78
+ const ELAPSED_REG = /\s\S*?\(\d+ms\)\S*/;
79
+ const NEWLINE_REG = /\r\n/g;
80
+ const LASTLINE_REG = /\n$/;
81
+
82
+ function sanitizeLog(str) {
83
+ let {
84
+ ansi,
85
+ elapsed
86
+ } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
87
+ // normalize line endings
88
+ str = str.replace(NEWLINE_REG, '\n'); // strip ansi colors
89
+
90
+ if (!ansi) str = str.replace(ANSI_REG, ''); // strip elapsed time
91
+
92
+ if (!elapsed) str = str.replace(ELAPSED_REG, ''); // strip trailing line endings
93
+
94
+ return str.replace(LASTLINE_REG, '');
95
+ }
96
+
97
+ function TestIO(data, options) {
98
+ if (!process.env.__PERCY_BROWSERIFIED__) {
99
+ let {
100
+ Writable
101
+ } = require$$2__default["default"];
102
+ return Object.assign(new Writable(), {
103
+ isTTY: options && options.isTTY,
104
+
105
+ cursorTo() {},
106
+
107
+ clearLine() {},
108
+
109
+ _write(chunk, encoding, callback) {
110
+ data.push(sanitizeLog(chunk.toString(), options));
111
+ callback();
112
+ }
113
+
114
+ });
115
+ }
116
+ }
117
+
118
+ function spy(object, method, func) {
119
+ if (object[method].reset) {
120
+ object[method].reset();
121
+ return object[method];
122
+ }
123
+
124
+ let spy = Object.assign(function spy() {
125
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
126
+ args[_key] = arguments[_key];
127
+ }
128
+
129
+ spy.calls.push(args);
130
+ if (func) return func.apply(this, args);
131
+ }, {
132
+ restore: () => object[method] = spy.originalValue,
133
+ reset: () => spy.calls.length = 0,
134
+ originalValue: object[method],
135
+ calls: []
136
+ });
137
+ object[method] = spy;
138
+ return spy;
139
+ }
140
+
141
+ const helpers = {
142
+ constructor: Logger,
143
+ loglevel: logger.loglevel,
144
+ stdout: [],
145
+ stderr: [],
146
+
147
+ get messages() {
148
+ return Logger.instance && Logger.instance.messages;
149
+ },
150
+
151
+ mock(options) {
152
+ helpers.reset();
153
+ helpers.options = options;
154
+
155
+ if (!process.env.__PERCY_BROWSERIFIED__) {
156
+ Logger.stdout = TestIO(helpers.stdout, options);
157
+ Logger.stderr = TestIO(helpers.stderr, options);
158
+ } else {
159
+ spy(Logger.prototype, 'write', function (lvl, msg) {
160
+ let stdio = lvl === 'info' ? 'stdout' : 'stderr';
161
+ helpers[stdio].push(sanitizeLog(msg, helpers.options));
162
+ return this.write.originalValue.call(this, lvl, msg);
163
+ });
164
+ spy(console, 'log');
165
+ spy(console, 'warn');
166
+ spy(console, 'error');
167
+ }
168
+ },
169
+
170
+ reset(soft) {
171
+ if (soft) Logger.instance.loglevel('info');else delete Logger.instance;
172
+ helpers.stdout.length = 0;
173
+ helpers.stderr.length = 0;
174
+
175
+ if (console.log.reset) {
176
+ console.log.reset();
177
+ console.warn.reset();
178
+ console.error.reset();
179
+ }
180
+ },
181
+
182
+ dump() {
183
+ if (!helpers.messages || !helpers.messages.size) return;
184
+ if (console.log.and) console.log.and.callThrough();
185
+
186
+ let write = m => process.env.__PERCY_BROWSERIFIED__ ? console.log(m) : process.stderr.write(`${m}\n`);
187
+
188
+ let logs = Array.from(helpers.messages);
189
+ logger.loglevel('debug');
190
+ write(logger.format('testing', 'warn', '--- DUMPING LOGS ---'));
191
+ logs.reduce((lastlog, _ref) => {
192
+ let {
193
+ debug,
194
+ level,
195
+ message,
196
+ timestamp
197
+ } = _ref;
198
+ write(logger.format(debug, level, message, timestamp - lastlog));
199
+ return timestamp;
200
+ }, logs[0].timestamp);
201
+ }
202
+
203
+ };
204
+ var helpers_1 = helpers;
205
+
206
+ return helpers_1;
207
+
208
+ })(PercyLogger, null);
209
+ }).call(window);
210
+
211
+ if (typeof define === "function" && define.amd) {
212
+ define([], () => window.PercyLogger.TestHelpers);
213
+ } else if (typeof module === "object" && module.exports) {
214
+ module.exports = window.PercyLogger.TestHelpers;
215
+ }