Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lib,test,doc: add option to highlight outputs of warn and error methods #40424

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions doc/api/console.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,19 @@ changes:
[`util.inspect()`][].
* `groupIndentation` {number} Set group indentation.
**Default:** `2`.
* `highlight` {Object} With this option you can highlight outputs of
[`console.warn()`][] and [`console.error()`][]. It's not enabled by default,
and only affects when coloring is enabled.
* `warn` {Object} Highlight options for [`console.warn()`][].
* `bgColor` {string} Background color used for highlighting, must be
one of the [`background colors`][] if not omitted.
* `indicator` {string} Optional leading string indicates if the output
is from [`console.warn()`][].
* `error` {Object} Highlight options for [`console.error()`][].
* `bgColor` {string} Background color used for highlighting, must be
one of the [`background colors`][] if not omitted.
* `indicator` {string} Optional leading string indicates if the output
is from [`console.error()`][].

Creates a new `Console` with one or two writable stream instances. `stdout` is a
writable stream to print log or info output. `stderr` is used for warning or
Expand All @@ -150,6 +163,25 @@ The global `console` is a special `Console` whose output is sent to
new Console({ stdout: process.stdout, stderr: process.stderr });
```

You can create your own `Console` with highlighting:

```js
const logger = new Console({
stdout: process.stdout,
stderr: process.stderr,
highlight: {
warn: { bgColor: 'bgYellowBright' },
error: { bgColor: 'bgRedBright', indicator: '\u274c' },
}
});

logger.warn('Warning!');
// Will output message 'Warning!' along with bright yellow background.
logger.error(new Error('Oops!'));
// Will output message 'Oops!' along with bright red background,
// and a unicode cross sign in front of the message indicating it is an error.
```

### `console.assert(value[, ...message])`

<!-- YAML
Expand Down Expand Up @@ -589,13 +621,15 @@ This method does not display anything unless used in the inspector. The
`console.timeStamp()` method adds an event with the label `'label'` to the
**Timeline** panel of the inspector.

[`background colors`]: util.md#background-colors
[`console.error()`]: #consoleerrordata-args
[`console.group()`]: #consolegrouplabel
[`console.log()`]: #consolelogdata-args
[`console.profile()`]: #consoleprofilelabel
[`console.profileEnd()`]: #consoleprofileendlabel
[`console.time()`]: #consoletimelabel
[`console.timeEnd()`]: #consoletimeendlabel
[`console.warn()`]: #consolewarndata-args
[`process.stderr`]: process.md#processstderr
[`process.stdout`]: process.md#processstdout
[`util.format()`]: util.md#utilformatformat-args
Expand Down
69 changes: 65 additions & 4 deletions lib/internal/console/constructor.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ const {
validateArray,
validateInteger,
validateObject,
validateOneOf,
validateString,
} = require('internal/validators');
const { previewEntries } = internalBinding('util');
const { Buffer: { isBuffer } } = require('buffer');
Expand Down Expand Up @@ -117,7 +119,8 @@ function Console(options /* or: stdout, stderr, ignoreErrors = true */) {
stderr = stdout,
ignoreErrors = true,
colorMode = 'auto',
inspectOptions,
highlight,
inspectOptions = {},
groupIndentation,
} = options;

Expand Down Expand Up @@ -147,6 +150,33 @@ function Console(options /* or: stdout, stderr, ignoreErrors = true */) {
optionsMap.set(this, inspectOptions);
}

// Now passing highlight object into console constructor,
// we can stylize 'warn' and 'error' output.
if (highlight !== undefined) {
validateObject(highlight, 'highlight');
const bgColors = ObjectKeys(inspect.colors)
.filter((k) => k.indexOf('bg') === 0);
if (highlight.warn !== undefined) {
validateObject(highlight.warn, 'highlight.warn');
if (highlight.warn.bgColor !== undefined) {
const bgColor = highlight.warn.bgColor;
validateOneOf(bgColor, 'highlight.warn.bgColor', bgColors);
}
if (highlight.warn.indicator !== undefined)
validateString(highlight.warn.indicator, 'highlight.warn.indicator');
}
if (highlight.error !== undefined) {
validateObject(highlight.error, 'highlight.error');
if (highlight.error.bgColor !== undefined) {
const bgColor = highlight.error.bgColor;
validateOneOf(bgColor, 'highlight.error.bgColor', bgColors);
}
if (highlight.error.indicator !== undefined)
validateString(highlight.error.indicator, 'highlight.error.indicator');
}
inspectOptions.highlight = highlight;
}

// Bind the prototype functions to this Console instance
ArrayPrototypeForEach(ObjectKeys(Console.prototype), (key) => {
// We have to bind the methods grabbed from the instance instead of from
Expand Down Expand Up @@ -255,7 +285,7 @@ ObjectDefineProperties(Console.prototype, {
},
[kWriteToConsole]: {
...consolePropAttributes,
value: function(streamSymbol, string) {
value: function(streamSymbol, string, opts = {}) {
const ignoreErrors = this._ignoreErrors;
const groupIndent = this[kGroupIndent];

Expand All @@ -270,6 +300,31 @@ ObjectDefineProperties(Console.prototype, {
}
string = groupIndent + string;
}

// Highlight output with background color and indicator
const inspectOptions = this[kGetInspectOptions](stream);
if (typeof inspectOptions.highlight === 'object' &&
inspectOptions.highlight !== null &&
typeof opts === 'object' &&
opts !== null &&
typeof opts.style === 'string') {
const style = inspectOptions.highlight[opts.style];
if (style !== undefined) {
const indicator = style.indicator ? style.indicator + ' ' : '';
string = `${indicator}${string}`;
if (inspectOptions.colors && style.bgColor !== undefined) {
const colors = inspect.colors[style.bgColor];
if (colors !== undefined) {
string = StringPrototypeReplace(string, /\n/g, '\u001b[K\n');
if (string.slice(-1) !== '\n') {
string += '\u001b[K';
}
string = `\u001b[${colors[0]}m${string}\u001b[${colors[1]}m`;
}
}
}
}

string += '\n';

if (ignoreErrors === false) return stream.write(string);
Expand Down Expand Up @@ -362,9 +417,16 @@ const consoleMethods = {


warn(...args) {
this[kWriteToConsole](kUseStderr, this[kFormatForStderr](args));
this[kWriteToConsole](kUseStderr,
this[kFormatForStderr](args),
{ style: 'warn' });
},

error(...args) {
this[kWriteToConsole](kUseStderr,
this[kFormatForStderr](args),
{ style: 'error' });
},

dir(object, options) {
this[kWriteToConsole](kUseStdout, inspect(object, {
Expand Down Expand Up @@ -666,7 +728,6 @@ for (const method of ReflectOwnKeys(consoleMethods))
Console.prototype.debug = Console.prototype.log;
Console.prototype.info = Console.prototype.log;
Console.prototype.dirxml = Console.prototype.log;
Console.prototype.error = Console.prototype.warn;
Console.prototype.groupCollapsed = Console.prototype.group;

module.exports = {
Expand Down
2 changes: 1 addition & 1 deletion test/parallel/test-repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,7 @@ const errorTests = [
'Object [console] {',
' log: [Function: log],',
' warn: [Function: warn],',
' error: [Function: error],',
' dir: [Function: dir],',
' time: [Function: time],',
' timeEnd: [Function: timeEnd],',
Expand All @@ -779,7 +780,6 @@ const errorTests = [
/ {2}debug: \[Function: (debug|log)],/,
/ {2}info: \[Function: (info|log)],/,
/ {2}dirxml: \[Function: (dirxml|log)],/,
/ {2}error: \[Function: (error|warn)],/,
/ {2}groupCollapsed: \[Function: (groupCollapsed|group)],/,
/ {2}Console: \[Function: Console],?/,
...process.features.inspector ? [
Expand Down
53 changes: 53 additions & 0 deletions test/pseudo-tty/test-console-highlighting-color.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use strict';
require('../common');
const assert = require('assert');
const { Console } = require('console');

// Work with 16 colors
process.env.FORCE_COLOR = 1;
delete process.env.NODE_DISABLE_COLORS;
delete process.env.NO_COLOR;

// Arguments validations
const invalidTypeError = {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: /must be of type/,
};
const mustBeOneOfError = {
code: 'ERR_INVALID_ARG_VALUE',
name: 'TypeError',
message: /must be one of/,
};
const options = {
stdout: process.stdout,
stderr: process.stderr,
ignoreErrors: true,
};

options.highlight = { warn: 'abc' };
assert.throws(() => { new Console(options); }, invalidTypeError);

options.highlight = { warn: { bgColor: 'abc' } };
assert.throws(() => { new Console(options); }, mustBeOneOfError);

options.highlight = { warn: { indicator: null } };
assert.throws(() => { new Console(options); }, invalidTypeError);

options.highlight = { error: 'abc' };
assert.throws(() => { new Console(options); }, invalidTypeError);

options.highlight = { error: { bgColor: 'abc' } };
assert.throws(() => { new Console(options); }, mustBeOneOfError);

options.highlight = { error: { indicator: null } };
assert.throws(() => { new Console(options); }, invalidTypeError);

options.highlight = {
warn: { bgColor: 'bgYellow', indicator: '[W]' },
error: { bgColor: 'bgRed', indicator: '[E]' },
};

const newInstance = new Console(options);
newInstance.warn(new Error('test'));
newInstance.error(new Error('test'));
17 changes: 17 additions & 0 deletions test/pseudo-tty/test-console-highlighting-color.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
*[43m[W] Error: test*[K
at * (*test-console-highlighting-color.js:*)*[K
* at *[K
* at *[K
* at *[K
* at *[K
* at *[K
* at *[K*[49m

*[41m[E] Error: test*[K
at * (*test-console-highlighting-color.js:*)*[K
* at *[K
* at *[K
* at *[K
* at *[K
* at *[K
* at *[K*[49m
22 changes: 22 additions & 0 deletions test/pseudo-tty/test-console-highlighting-no-color.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';
require('../common');
const { Console } = require('console');

// Work with no color
process.env.NODE_DISABLE_COLORS = true;
process.env.FORCE_COLOR = 0;

const options = {
stdout: process.stdout,
stderr: process.stderr,
ignoreErrors: true,
highlight: {
// Indicators should always work even if no coloring enabled
warn: { bgColor: 'bgYellow', indicator: '[W]' },
error: { bgColor: 'bgRed', indicator: '[E]' },
}
};

const newInstance = new Console(options);
newInstance.warn(new Error('test'));
newInstance.error(new Error('test'));
17 changes: 17 additions & 0 deletions test/pseudo-tty/test-console-highlighting-no-color.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[W] Error: test
at * (*test-console-highlighting-no-color.js:*)
* at *
* at *
* at *
* at *
* at *
* at *

[E] Error: test
at * (*test-console-highlighting-no-color.js:*)
* at *
* at *
* at *
* at *
* at *
* at *