Skip to content

Commit 3f1562d

Browse files
addaleaxjasnell
authored andcommitted
console: add color support
Add a way to tell `Console` instances to either always use, never use or auto-detect color support and inspect objects accordingly. PR-URL: #19372 Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 4fe5175 commit 3f1562d

File tree

5 files changed

+106
-17
lines changed

5 files changed

+106
-17
lines changed

doc/api/console.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,20 @@ changes:
8787
description: The `ignoreErrors` option was introduced.
8888
- version: REPLACEME
8989
pr-url: https://github.com/nodejs/node/pull/19372
90-
description: The `Console` constructor now supports an `options` argument.
90+
description: The `Console` constructor now supports an `options` argument,
91+
and the `colorMode` option was introduced.
9192
-->
9293

9394
* `options` {Object}
9495
* `stdout` {stream.Writable}
9596
* `stderr` {stream.Writable}
9697
* `ignoreErrors` {boolean} Ignore errors when writing to the underlying
9798
streams. **Default:** `true`.
99+
* `colorMode` {boolean|string} Set color support for this `Console` instance.
100+
Setting to `true` enables coloring while inspecting values, setting to
101+
`'auto'` will make color support depend on the value of the `isTTY` property
102+
and the value returned by `getColorDepth()` on the respective stream.
103+
**Default:** `false`
98104

99105
Creates a new `Console` with one or two writable stream instances. `stdout` is a
100106
writable stream to print log or info output. `stderr` is used for warning or

doc/api/util.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,8 @@ an `inspectOptions` argument which specifies options that are passed along to
268268

269269
```js
270270
util.formatWithOptions({ colors: true }, 'See object %O', { foo: 42 });
271-
// Returns 'See object { foo: 42 }', where `42` is colored as a number
272-
// when printed to a terminal.
271+
// Returns 'See object { foo: 42 }', where `42` is colored as a number
272+
// when printed to a terminal.
273273
```
274274

275275
## util.getSystemErrorName(err)

lib/console.js

+49-14
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const {
2626
codes: {
2727
ERR_CONSOLE_WRITABLE_STREAM,
2828
ERR_INVALID_ARG_TYPE,
29+
ERR_INVALID_ARG_VALUE,
2930
},
3031
} = require('internal/errors');
3132
const { previewMapIterator, previewSetIterator } = require('internal/v8');
@@ -49,24 +50,32 @@ const {
4950
} = Array;
5051

5152
// Track amount of indentation required via `console.group()`.
52-
const kGroupIndent = Symbol('groupIndent');
53+
const kGroupIndent = Symbol('kGroupIndent');
54+
55+
const kFormatForStderr = Symbol('kFormatForStderr');
56+
const kFormatForStdout = Symbol('kFormatForStdout');
57+
const kGetInspectOptions = Symbol('kGetInspectOptions');
58+
const kColorMode = Symbol('kColorMode');
5359

5460
function Console(options /* or: stdout, stderr, ignoreErrors = true */) {
5561
if (!(this instanceof Console)) {
5662
return new Console(...arguments);
5763
}
5864

59-
let stdout, stderr, ignoreErrors;
65+
let stdout, stderr, ignoreErrors, colorMode;
6066
if (options && typeof options.write !== 'function') {
6167
({
6268
stdout,
6369
stderr = stdout,
64-
ignoreErrors = true
70+
ignoreErrors = true,
71+
colorMode = false
6572
} = options);
6673
} else {
67-
stdout = options;
68-
stderr = arguments[1];
69-
ignoreErrors = arguments[2] === undefined ? true : arguments[2];
74+
return new Console({
75+
stdout: options,
76+
stderr: arguments[1],
77+
ignoreErrors: arguments[2]
78+
});
7079
}
7180

7281
if (!stdout || typeof stdout.write !== 'function') {
@@ -94,7 +103,11 @@ function Console(options /* or: stdout, stderr, ignoreErrors = true */) {
94103
prop.value = createWriteErrorHandler(stderr);
95104
Object.defineProperty(this, '_stderrErrorHandler', prop);
96105

106+
if (typeof colorMode !== 'boolean' && colorMode !== 'auto')
107+
throw new ERR_INVALID_ARG_VALUE('colorMode', colorMode);
108+
97109
this[kCounts] = new Map();
110+
this[kColorMode] = colorMode;
98111

99112
Object.defineProperty(this, kGroupIndent, { writable: true });
100113
this[kGroupIndent] = '';
@@ -156,13 +169,33 @@ function write(ignoreErrors, stream, string, errorhandler, groupIndent) {
156169
}
157170
}
158171

172+
const kColorInspectOptions = { colors: true };
173+
const kNoColorInspectOptions = {};
174+
Console.prototype[kGetInspectOptions] = function(stream) {
175+
let color = this[kColorMode];
176+
if (color === 'auto') {
177+
color = stream.isTTY && (
178+
typeof stream.getColorDepth === 'function' ?
179+
stream.getColorDepth() > 2 : true);
180+
}
181+
182+
return color ? kColorInspectOptions : kNoColorInspectOptions;
183+
};
184+
185+
Console.prototype[kFormatForStdout] = function(args) {
186+
const opts = this[kGetInspectOptions](this._stdout);
187+
return util.formatWithOptions(opts, ...args);
188+
};
189+
190+
Console.prototype[kFormatForStderr] = function(args) {
191+
const opts = this[kGetInspectOptions](this._stderr);
192+
return util.formatWithOptions(opts, ...args);
193+
};
194+
159195
Console.prototype.log = function log(...args) {
160196
write(this._ignoreErrors,
161197
this._stdout,
162-
// The performance of .apply and the spread operator seems on par in V8
163-
// 6.3 but the spread operator, unlike .apply(), pushes the elements
164-
// onto the stack. That is, it makes stack overflows more likely.
165-
util.format.apply(null, args),
198+
this[kFormatForStdout](args),
166199
this._stdoutErrorHandler,
167200
this[kGroupIndent]);
168201
};
@@ -173,14 +206,16 @@ Console.prototype.dirxml = Console.prototype.log;
173206
Console.prototype.warn = function warn(...args) {
174207
write(this._ignoreErrors,
175208
this._stderr,
176-
util.format.apply(null, args),
209+
this[kFormatForStderr](args),
177210
this._stderrErrorHandler,
178211
this[kGroupIndent]);
179212
};
180213
Console.prototype.error = Console.prototype.warn;
181214

182215
Console.prototype.dir = function dir(object, options) {
183-
options = Object.assign({ customInspect: false }, options);
216+
options = Object.assign({
217+
customInspect: false
218+
}, this[kGetInspectOptions](this._stdout), options);
184219
write(this._ignoreErrors,
185220
this._stdout,
186221
util.inspect(object, options),
@@ -211,7 +246,7 @@ Console.prototype.timeEnd = function timeEnd(label = 'default') {
211246
Console.prototype.trace = function trace(...args) {
212247
const err = {
213248
name: 'Trace',
214-
message: util.format.apply(null, args)
249+
message: this[kFormatForStderr](args)
215250
};
216251
Error.captureStackTrace(err, trace);
217252
this.error(err.stack);
@@ -220,7 +255,7 @@ Console.prototype.trace = function trace(...args) {
220255
Console.prototype.assert = function assert(expression, ...args) {
221256
if (!expression) {
222257
args[0] = `Assertion failed${args.length === 0 ? '' : `: ${args[0]}`}`;
223-
this.warn(util.format.apply(null, args));
258+
this.warn(this[kFormatForStderr](args));
224259
}
225260
};
226261

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const util = require('util');
5+
const { Writable } = require('stream');
6+
const { Console } = require('console');
7+
8+
function check(isTTY, colorMode, expectedColorMode) {
9+
const items = [
10+
1,
11+
{ a: 2 },
12+
[ 'foo' ],
13+
{ '\\a': '\\bar' }
14+
];
15+
16+
let i = 0;
17+
const stream = new Writable({
18+
write: common.mustCall((chunk, enc, cb) => {
19+
assert.strictEqual(chunk.trim(),
20+
util.inspect(items[i++], {
21+
colors: expectedColorMode
22+
}));
23+
cb();
24+
}, items.length),
25+
decodeStrings: false
26+
});
27+
stream.isTTY = isTTY;
28+
29+
// Set ignoreErrors to `false` here so that we see assertion failures
30+
// from the `write()` call happen.
31+
const testConsole = new Console({
32+
stdout: stream,
33+
ignoreErrors: false,
34+
colorMode
35+
});
36+
for (const item of items) {
37+
testConsole.log(item);
38+
}
39+
}
40+
41+
check(true, 'auto', true);
42+
check(false, 'auto', false);
43+
check(true, true, true);
44+
check(false, true, true);
45+
check(true, false, false);
46+
check(false, false, false);

test/parallel/test-console.js

+2
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,11 @@ const custom_inspect = { foo: 'bar', inspect: () => 'inspect' };
5151

5252
const strings = [];
5353
const errStrings = [];
54+
process.stdout.isTTY = false;
5455
common.hijackStdout(function(data) {
5556
strings.push(data);
5657
});
58+
process.stderr.isTTY = false;
5759
common.hijackStderr(function(data) {
5860
errStrings.push(data);
5961
});

0 commit comments

Comments
 (0)