From 76f730c891ae6ada016cdca5083f0feab8dbd86b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Trys=C5=82a?= Date: Tue, 13 Aug 2019 15:33:01 +0200 Subject: [PATCH 1/4] improve logging to file --- packages/haul-core/package.json | 2 + packages/haul-core/src/runtime/Logger.ts | 61 ++++++++++++++++--- .../haul-core/src/server/NonInteractiveUI.ts | 10 ++- packages/haul-core/src/server/Server.ts | 17 ++++-- yarn.lock | 21 ++++--- 5 files changed, 88 insertions(+), 23 deletions(-) diff --git a/packages/haul-core/package.json b/packages/haul-core/package.json index 180c7116..e75c5935 100644 --- a/packages/haul-core/package.json +++ b/packages/haul-core/package.json @@ -47,6 +47,7 @@ "resolve": "^1.12.0", "semver": "^6.3.0", "source-map": "^0.7.3", + "strip-ansi": "5.2.0", "terminal-kit": "^1.30.0", "terser": "^4.1.3", "utility-types": "^3.7.0", @@ -68,6 +69,7 @@ "@types/node-fetch": "^2.5.0", "@types/resolve": "^0.0.8", "@types/semver": "^6.0.1", + "@types/strip-ansi": "^5.2.1", "@types/terminal-kit": "^1.28.0", "@types/ws": "^6.0.2" } diff --git a/packages/haul-core/src/runtime/Logger.ts b/packages/haul-core/src/runtime/Logger.ts index 5ba048c4..87093c3b 100644 --- a/packages/haul-core/src/runtime/Logger.ts +++ b/packages/haul-core/src/runtime/Logger.ts @@ -1,6 +1,7 @@ import { inspect } from 'util'; import fs from 'fs'; import path from 'path'; +import stripAnsi from 'strip-ansi'; import { LoggerEvent } from '@haul-bundler/inspector-events'; import { container, @@ -48,6 +49,11 @@ export default class Logger { constructor(private inspectorClient?: InspectorClient) {} + /** + * Enables logging all messages to file as well as to process' STDOUT. + * If `json` is `true` each log will be in JSON format for easier processing. + * If relative `filename` is passed, it will be resolved based on process' CWD. + */ enableFileLogging(filename: string, { json }: { json: boolean }) { const absFilename = path.isAbsolute(filename) ? filename @@ -56,6 +62,11 @@ export default class Logger { this.logAsJson = json; } + /** + * Disposes the logger by closing all open handles. + * If file logging was enabled, the file descriptor will be closed here. + * Should always be called before exiting from process. + */ dispose() { if (this.logFile !== undefined) { fs.closeSync(this.logFile); @@ -68,6 +79,11 @@ export default class Logger { done = this.createLoggingFunction(LoggerLevel.Done); debug = this.createLoggingFunction(LoggerLevel.Debug); + /** + * Enables proxy for all logs. + * Messages will be passed to `handler` function and __won't be logged__ to process' STDOUT. + * Returns a dispose function to disable the proxy. + */ proxy = (handler: ProxyHandler): (() => void) => { this.proxyHandler = handler; return () => { @@ -75,20 +91,33 @@ export default class Logger { }; }; + /** + * Prints arguments _as is_ without any processing. + */ print = (...args: unknown[]) => { // eslint-disable-next-line no-console console.log(...args); }; + /** + * Enhances given arguments with ANSI color. + */ enhanceWithColor = (enhancer: AnsiColor, ...args: unknown[]) => { return color(enhancer, this.stringify(args).join(' ')).build(); }; + /** + * Enhances given arguments with ANSI modifier, for example with `bold`, `italic` etc. + */ enhanceWithModifier = (enhancer: AnsiModifier, ...args: unknown[]) => { return modifier(enhancer, this.stringify(args).join(' ')).build(); }; - enhance = (level: LoggerLevel, ...args: unknown[]) => { + /** + * Enhances given arguments with level prefix. + * Example: info ▶︎ argument1 argument2 + */ + enhanceWithLevel = (level: LoggerLevel, ...args: unknown[]) => { return container( color(levelToColorMappings[level], modifier('bold', level)), pad(1), @@ -98,8 +127,20 @@ export default class Logger { ).build(); }; + /** + * Stringify array of elements into a string array. + * Uses Node's built-in `util.inspect` function to stringify non-string elements. + */ stringify(args: any[]) { - return args.map(item => (typeof item === 'string' ? item : inspect(item))); + return args.map(item => + typeof item === 'string' + ? item + : inspect(item, { + depth: null, + maxArrayLength: null, + breakLength: Infinity, + }) + ); } private createLoggingFunction(level: LoggerLevel) { @@ -109,13 +150,19 @@ export default class Logger { } if (this.logFile !== undefined) { + const rawArgs = this.stringify(args).map(stripAnsi); fs.appendFileSync( this.logFile, (this.logAsJson - ? JSON.stringify({ timestamp: new Date(), level, messages: args }) - : `[${new Date().toISOString()}] ${level}: ${this.stringify( - args - ).join()}`) + '\n', + ? stripAnsi( + JSON.stringify({ + timestamp: new Date(), + level, + messages: rawArgs, + }) + ) + : `[${new Date().toISOString()}] ${level}: ${rawArgs.join()}`) + + '\n', 'utf8' ); } @@ -127,7 +174,7 @@ export default class Logger { if (this.proxyHandler) { this.proxyHandler(level, ...args); } else { - this.print(this.enhance(level, ...args)); + this.print(this.enhanceWithLevel(level, ...args)); } } }; diff --git a/packages/haul-core/src/server/NonInteractiveUI.ts b/packages/haul-core/src/server/NonInteractiveUI.ts index aa7f810b..38083379 100644 --- a/packages/haul-core/src/server/NonInteractiveUI.ts +++ b/packages/haul-core/src/server/NonInteractiveUI.ts @@ -10,15 +10,19 @@ export default class NonInteractiveUserInterface implements UserInterface { ) { if (running) { const percent = `${Math.floor(Math.min(1, value) * 100)}%`; - this.runtime.logger.info(`Compilation - running (${percent})`); + this.runtime.logger.info( + `[${platform.toUpperCase()}] Compilation - running (${percent})` + ); } else { - this.runtime.logger.info(`Compilation - idle`); + this.runtime.logger.info( + `[${platform.toUpperCase()}] Compilation - idle` + ); } } addLogItem(item: string) { // `item` should be already enhanced with ANSI escape codes - this.runtime.logger.print(item); + this.runtime.logger.print('print', item); } dispose(exitCode: number = 0, exit: boolean = true) { diff --git a/packages/haul-core/src/server/Server.ts b/packages/haul-core/src/server/Server.ts index 9b1a7c2b..584e763b 100644 --- a/packages/haul-core/src/server/Server.ts +++ b/packages/haul-core/src/server/Server.ts @@ -93,7 +93,7 @@ export default class Server { value: 0, }); this.ui.addLogItem( - this.runtime.logger.enhance(Logger.Level.Error, message) + this.runtime.logger.enhanceWithLevel(Logger.Level.Error, message) ); } ); @@ -107,7 +107,7 @@ export default class Server { }); errors.forEach(error => { this.ui.addLogItem( - this.runtime.logger.enhance(Logger.Level.Error, error) + this.runtime.logger.enhanceWithLevel(Logger.Level.Error, error) ); }); } @@ -142,7 +142,9 @@ export default class Server { // in interactive mode. if (!this.options.noInteractive) { this.disposeLoggerProxy = this.runtime.logger.proxy((level, ...args) => { - this.ui.addLogItem(this.runtime.logger.enhance(level, ...args)); + this.ui.addLogItem( + this.runtime.logger.enhanceWithLevel(level, ...args) + ); }); } @@ -219,13 +221,13 @@ export default class Server { console.log = (...args) => { this.ui.addLogItem( - this.runtime.logger.enhance(Logger.Level.Info, ...args) + this.runtime.logger.enhanceWithLevel(Logger.Level.Info, ...args) ); }; console.error = (...args) => { this.ui.addLogItem( - this.runtime.logger.enhance(Logger.Level.Error, ...args) + this.runtime.logger.enhanceWithLevel(Logger.Level.Error, ...args) ); }; @@ -239,12 +241,15 @@ export default class Server { logServerEvent(request: Hapi.Request, event?: Hapi.RequestEvent) { const { statusCode } = request.response as ResponseObject; let logColor: AnsiColor = 'green'; + let level: 'info' | 'warn' | 'error' = 'info'; if (statusCode >= 300 && statusCode < 400) { logColor = 'yellow'; + level = 'warn'; } else if (statusCode >= 400) { logColor = 'red'; + level = 'error'; } - this.ui.addLogItem( + this.runtime.logger[level]( container( color(logColor, modifier('bold', request.method.toUpperCase())), pad(1), diff --git a/yarn.lock b/yarn.lock index 083111e3..4d488693 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3225,6 +3225,13 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== +"@types/strip-ansi@^5.2.1": + version "5.2.1" + resolved "https://registry.yarnpkg.com/@types/strip-ansi/-/strip-ansi-5.2.1.tgz#acd97f1f091e332bb7ce697c4609eb2370fa2a92" + integrity sha512-1l5iM0LBkVU8JXxnIoBqNvg+yyOXxPeN6DNoD+7A9AN1B8FhYPSeIXgyNqwIqg1uzipTgVC2hmuDzB0u9qw/PA== + dependencies: + strip-ansi "*" + "@types/tapable@*": version "1.0.4" resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.4.tgz#b4ffc7dc97b498c969b360a41eee247f82616370" @@ -10600,6 +10607,13 @@ stringstream@~0.0.4: version "0.0.6" resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72" +strip-ansi@*, strip-ansi@5.2.0, strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -10612,13 +10626,6 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - strip-bom@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" From 199eb5b075ce9d0d224484f863b28a11b19d0d0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Trys=C5=82a?= Date: Tue, 13 Aug 2019 15:45:33 +0200 Subject: [PATCH 2/4] wrap lines based on terminal width --- packages/haul-core/package.json | 2 + .../haul-core/src/server/InteractiveUI.ts | 16 ++---- yarn.lock | 52 +++++++++++++++++++ 3 files changed, 58 insertions(+), 12 deletions(-) diff --git a/packages/haul-core/package.json b/packages/haul-core/package.json index e75c5935..9f381c4b 100644 --- a/packages/haul-core/package.json +++ b/packages/haul-core/package.json @@ -52,6 +52,7 @@ "terser": "^4.1.3", "utility-types": "^3.7.0", "webpack": "^4.39.1", + "wrap-ansi": "^6.0.0", "ws": "^6.2.1" }, "devDependencies": { @@ -71,6 +72,7 @@ "@types/semver": "^6.0.1", "@types/strip-ansi": "^5.2.1", "@types/terminal-kit": "^1.28.0", + "@types/wrap-ansi": "^3.0.0", "@types/ws": "^6.0.2" } } diff --git a/packages/haul-core/src/server/InteractiveUI.ts b/packages/haul-core/src/server/InteractiveUI.ts index af613694..d0f3f96a 100644 --- a/packages/haul-core/src/server/InteractiveUI.ts +++ b/packages/haul-core/src/server/InteractiveUI.ts @@ -1,5 +1,6 @@ import { Terminal } from 'terminal-kit'; import { container, color, modifier, pad } from 'ansi-fragments'; +import wrapAnsi from 'wrap-ansi'; import UserInterface from './UI'; class Logs { @@ -27,17 +28,8 @@ class Logs { addItem(item: string) { const lines = item.split('\n').reduce( (acc, line) => { - if (line.length > this.maxLineWidth) { - const subLines = - line.match(new RegExp(`.{1,${this.maxLineWidth}}`, 'g')) || []; - if (subLines) { - return acc.concat(...subLines); - } - - return acc; - } - - return acc.concat(line); + const wrappedLine = wrapAnsi(line, this.maxLineWidth); + return acc.concat(...wrappedLine.split('\n')); }, [] as string[] ); @@ -180,7 +172,7 @@ export default class InteractiveUserInterface implements UserInterface { start(platforms: string[]) { this.logs.sliceStart = 0; this.logs.sliceMaxLength = this.terminal.height - platforms.length - 6; - this.logs.maxLineWidth = this.terminal.width - 10; + this.logs.maxLineWidth = this.terminal.width - 2; this.terminal.fullscreen(true); this.terminal.grabInput({ mouse: 'motion' }); diff --git a/yarn.lock b/yarn.lock index 4d488693..583c53e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3302,6 +3302,11 @@ resolved "https://registry.yarnpkg.com/@types/which/-/which-1.3.1.tgz#7802c380887986ca909008afea4e08025b130f8d" integrity sha512-ZrJDWpvg75LTGX4XwuneY9s6bF3OeZcGTpoGh3zDV9ytzcHMFsRrMIaLBRJZQMBoGyKs6unBQfVdrLZiYfb1zQ== +"@types/wrap-ansi@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd" + integrity sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g== + "@types/ws@^6.0.2": version "6.0.2" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-6.0.2.tgz#f3340f7e3d7a07104a5dbcaa8ada4e8d2d45eecb" @@ -3696,6 +3701,13 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" +ansi-styles@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.0.0.tgz#f6b84e8fc97ea7add7a53b7530ef28f3fde0e048" + integrity sha512-8zjUtFJ3db/QoPXuuEMloS2AUf79/yeyttJ7Abr3hteopJu9HK8vsgGviGUMq+zyA6cZZO6gAyZoMTF6TgaEjA== + dependencies: + color-convert "^2.0.0" + any-promise@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -4541,10 +4553,22 @@ color-convert@^1.9.0: dependencies: color-name "1.1.3" +color-convert@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.0.tgz#9851ac61cc0d3898a8a3088650d5bf447bf69d97" + integrity sha512-hzTicsCJIHdxih9+2aLR1tNGZX5qSJGRHDPVwSY26tVrEf55XNajLOBWz2UuWSIergszA09/bqnOiHyqx9fxQg== + dependencies: + color-name "~1.1.4" + color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + colorette@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.0.7.tgz#7adf43c445ee63a541b4a4aef7d13f03df1e0cc0" @@ -5213,6 +5237,11 @@ emoji-regex@^7.0.1: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + emojis-list@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" @@ -6717,6 +6746,11 @@ is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + is-generator-fn@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" @@ -10577,6 +10611,15 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" +string-width@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.1.0.tgz#ba846d1daa97c3c596155308063e075ed1c99aff" + integrity sha512-NrX+1dVVh+6Y9dnQ19pR0pP4FiEIlUvdTGn8pw6CKTNq5sgib2nIhmUNT5TAmhWmvKr3WcxBcP3E8nWezuipuQ== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^5.2.0" + string_decoder@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -11457,6 +11500,15 @@ wrap-ansi@^5.1.0: string-width "^3.0.0" strip-ansi "^5.0.0" +wrap-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.0.0.tgz#47c7b7329e0b8000f5756b0693a861e357e4043e" + integrity sha512-8YwLklVkHe4QNpGFrK6Mxm+BaMY7da6C9GlDED3xs3XwThyJHSbVwg9qC4s1N8tBFcnM1S0s8I390RC6SgGe+g== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^5.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" From 0aefe8062d77395d4618d54d958b553a7b75bbc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Trys=C5=82a?= Date: Tue, 13 Aug 2019 15:53:46 +0200 Subject: [PATCH 3/4] force line wrapping an a given width --- packages/haul-core/src/server/InteractiveUI.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/haul-core/src/server/InteractiveUI.ts b/packages/haul-core/src/server/InteractiveUI.ts index d0f3f96a..7c022443 100644 --- a/packages/haul-core/src/server/InteractiveUI.ts +++ b/packages/haul-core/src/server/InteractiveUI.ts @@ -28,7 +28,9 @@ class Logs { addItem(item: string) { const lines = item.split('\n').reduce( (acc, line) => { - const wrappedLine = wrapAnsi(line, this.maxLineWidth); + const wrappedLine = wrapAnsi(line, this.maxLineWidth, { + hard: true, + }); return acc.concat(...wrappedLine.split('\n')); }, [] as string[] From df638fa4648dc44bc9b044ed5e817f75bec00834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Trys=C5=82a?= Date: Tue, 13 Aug 2019 16:00:26 +0200 Subject: [PATCH 4/4] join logs in file with space between messages --- packages/haul-core/src/runtime/Logger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/haul-core/src/runtime/Logger.ts b/packages/haul-core/src/runtime/Logger.ts index 87093c3b..59d56b84 100644 --- a/packages/haul-core/src/runtime/Logger.ts +++ b/packages/haul-core/src/runtime/Logger.ts @@ -161,7 +161,7 @@ export default class Logger { messages: rawArgs, }) ) - : `[${new Date().toISOString()}] ${level}: ${rawArgs.join()}`) + + : `[${new Date().toISOString()}] ${level}: ${rawArgs.join(' ')}`) + '\n', 'utf8' );