console 模块提供了简单的调试功能,这在一些测试调试中常是最方便且使用最多的工具。它类似于浏览器中的 console,但有一个关键区别:在浏览器中它是同步的,而在 node.js 中,它是否同步或异步取决于操作系统和输出流类型。
本文参考了官方源码,实现了一个简化的 Console 模块版本,介绍了其基本用法,并提出了常见的问题,供大家学习和参考。具体内容请详见下文。
作者简介:五月君,Node.js 开发者,热爱技术并乐于分享的 90 后青年,公众号「Nodejs技术栈」的作者,GitHub 开源项目维护者,网址为 https://www.php.cn/link/d2450555f921f45a0c0bf6f41bd95a3d。
快速导航
console 是同步的还是异步的?,参考:#如何实现一个 console.log?,参考:#为什么 console.log() 执行完后就退出?,参考:#Logger 模块实现步骤
创建 logger.js 文件 以下是 logger.js 文件的代码:
const util = require('util');
<p>/**</p><ul><li>初始化 Logger 对象</li><li>@param {*} stdout</li><li>@param {<em>} stderr
</em>/
function Logger(stdout, stderr) {
// 检查当前对象是否为 Logger 实例
if (!(this instanceof Logger)) {
return new Logger(stdout, stderr);
}
// 检查是否是一个可写流实例
if (!stdout || !(stdout.write instanceof Function)) {
throw new Error('Logger expects a writable stream instance');
}
// 如果 stderr 未指定,使用 stdout
if (!stderr) {
stderr = stdout;
}
// 设置 JavaScript 对象的属性
let props = {
writable: true, // 对象属性是否可修改,false 为不可修改,默认值为 true
enumerable: false, // 对象属性是否可通过 for-in 循环,false 为不可循环,默认值为 true
configurable: false, // 能否使用 delete、能否修改属性特性、或能否修改访问器属性,false 为不可重新定义,默认值为 true
};
// Logger 对象定义 _stdout 属性
Object.defineProperty(this, '_stdout', Object.assign(props, {
value: stdout,
}));
// Logger 对象定义 _stderr 属性
Object.defineProperty(this, '_stderr', Object.assign(props, {
value: stderr,
}));
// Logger 对象定义 _times 属性
Object.defineProperty(this, '_times', Object.assign(props, {
value: new Map(),
}));
// 将原型方法上的属性绑定到 Logger 实例上
const keys = Object.keys(Logger.prototype);
for (let k in keys) {
this[keys[k]] = this[keys[k]].bind(this);
}
}</li></ul><p>// 定义原型 Logger 的 log 方法
Logger.prototype.log = function() {
this._stdout.write(util.format.apply(this, arguments) + '\n');
};</p><p>Logger.prototype.info = Logger.prototype.log;</p><p>// 定义原型 Logger 的 warn 方法
Logger.prototype.warn = function() {
this._stderr.write(util.format.apply(this, arguments) + <code>\n);
};</p><p>Logger.prototype.error = Logger.prototype.warn;</p>
<div class="aritcle_card">
<a class="aritcle_card_img" href="/ai/2249">
<img src="https://img.php.cn/upload/ai_manual/000/000/000/175679998691134.png" alt="超级简历WonderCV">
</a>
<div class="aritcle_card_info">
<a href="/ai/2249">超级简历WonderCV</a>
<p>免费求职简历模版下载制作,应届生职场人必备简历制作神器</p>
<div class="">
<img src="/static/images/card_xiazai.png" alt="超级简历WonderCV">
<span>150</span>
</div>
</div>
<a href="/ai/2249" class="aritcle_card_btn">
<span>查看详情</span>
<img src="/static/images/cardxiayige-3.png" alt="超级简历WonderCV">
</a>
</div>
<p>// 返回当前调用堆栈信息
Logger.prototype.trace = function trace(...args) {
const err = {
name: 'Trace',
message: util.format.apply(null, args)
};
// 源自 V8 引擎的 Stack Trace API <a href="https://www.php.cn/link/a41c3b5da4b5a60a165094e92c46da60">https://www.php.cn/link/a41c3b5da4b5a60a165094e92c46da60</a>
Error.captureStackTrace(err, trace);
this.error(err.stack);
};</p><p>// 清除控制台信息
Logger.prototype.clear = function() {
// 如果 stdout 输出是一个控制台,进行 clear 否则不进行处理
if (this._stdout.isTTY) {
const { cursorTo, clearScreenDown } = require('readline');
cursorTo(this._stdout, 0, 0); // 移动光标到给定的 TTY stream 中指定的位置。
clearScreenDown(this._stdout); // 方法会从光标的当前位置向下清除给定的 TTY 流
}
};</p><p>// 直接输出某个对象
Logger.prototype.dir = function(object, options) {
options = Object.assign({ customInspect: false }, options);
/**</p><ul><li>util.inspect(object,[showHidden],[depth],[colors]) 是一个将任意对象转换为字符串的方法,通常用于调试和错误的输出。</li><li>showhidden - 是一个可选参数,如果值为 true,将会输出更多隐藏信息。</li><li>depth - 表示最大递归的层数。如果对象很复杂,可以指定层数控制输出信息的多少。</li><li>如果不指定 depth, 默认会递归 3 层,指定为 null 表示不限递归层数完整遍历对象。</li><li>如果 color = true,输出格式将会以 ansi 颜色编码,通常用于在终端显示更漂亮的效果。
*/
this._stdout.write(util.inspect(object, options) + '\n');
};</li></ul><p>// 计时器开始时间
Logger.prototype.time = function(label) {
// process.hrtime() 方法返回当前时间以 [seconds, nanoseconds] tuple Array 表示的高精度解析值, nanoseconds 是当前时间无法使用秒的精度表示的剩余部分。
this._times.set(label, process.hrtime());
};</p><p>// 计时器结束时间
Logger.prototype.timeEnd = function(label) {
const time = this._times.get(label);
if (!time) {
process.emitWarning(<code>No such label '${label}' for console.timeEnd()</code>);
return;
}
const duration = process.hrtime(time);
const ms = duration[0] <em> 1000 + duration[1] / 1e6; // 1e6 = 1000000.0 1e6 表示 1</em>10^6
this.log('%s: %sms', label, ms.toFixed(3));
this._times.delete(label);
};</p><p>module.exports = new Logger(process.stdout, process.stderr);
module.exports.Logger = Logger;</code>Logger 模块基本使用
日志输出至终端 无特殊说明,日志都是默认打印到控制台,在一些代码调试中也是用的最多的。
const logger = require('logger');
logger.log('hello world'); // 普通日志打印
logger.info('hello world'); // 等同于 logger.log
logger.error('hello world'); // 错误日志打印
logger.warn('hello world'); // 等同于 logger.error
logger.clear(); // 清除控制台信息日志输出至文件 定义要输出的日志文件,实例化我们自定义的 Logger 对象。
const fs = require('fs');
const output = fs.createWriteStream('./stdout.txt');
const errorOutput = fs.createWriteStream('./stderr.txt');
const { Logger } = require('./logger');
const logger = new Logger(output, errorOutput);
logger.info('hello world!'); // 内容输出到 stdout.txt 文件
logger.error('错误日志记录'); // 内容输出到 stderr.txt 文件版本问题 将日志信息打印到本地指定文件,这里要注意版本问题,以下代码示例在 Node.js v10.x 以下版本可以运行,但在 Node.js v10.x 及以上版本可能会报错,具体原因请参见 https://www.php.cn/link/d3416acbe6cd441c5fea6bf3a9816cd9。
TypeError: Console expects a writable stream instance at new Console (console.js:35:11) at Object.<anonymous> (/Users/ryzokuken/Code/temp/node/console/21366.js:11:16) at Module._compile (module.js:652:30) at Object.Module._extensions..js (module.js:663:10) at Module.load (module.js:565:32) at tryModuleLoad (module.js:505:12) at Function.Module._load (module.js:497:3) at Function.Module.runMain (module.js:693:10) at startup (bootstrap_node.js:188:16) at bootstrap_node.js:609:3
trace 打印错误堆栈
logger.trace('测试错误');输出如下:
Trace: 测试错误 at Object.<anonymous> (/Users/qufei/Documents/mycode/Summarize/test/console-test.js:7:8) at Module._compile (module.js:624:30) at Object.Module._extensions..js (module.js:635:10) at Module.load (module.js:545:32) at tryModuleLoad (module.js:508:12) at Function.Module._load (module.js:500:3) at Function.Module.runMain (module.js:665:10) at startup (bootstrap_node.js:201:16) at bootstrap_node.js:626:3
dir 显示一个对象的所有属性和方法 depth - 表示最大递归的层数。如果对象很复杂,可以指定层数控制输出信息的多少。
const family = {
name: 'Jack',
brother: {
hobby: ['篮球', '足球']
}
};
logger.dir(family, {depth: 3});
// { name: 'Jack', brother: { hobby: [ '篮球', '足球' ] } }计算程序执行消耗时间 logger.time 和 logger.timeEnd 用来测量一个 JavaScript 脚本程序执行消耗的时间,单位是毫秒。
// 启动计时器
logger.time('计时器');
// 中间写一些测试代码
for (let i = 0; i < 1000000; i++) {}
// 结束计时器
logger.timeEnd('计时器');
// 计时器: 12.345ms面试指南
console 是同步的还是异步的? console 既不是总是同步的,也不是总是异步的。是否为同步取决于链接的是什么流以及操作系统是 Windows 还是 POSIX:
注意: 同步写将会阻塞事件循环直到写完成。有时可能一瞬间就能写到一个文件,但当系统处于高负载时,管道的接收端可能不会被读取、缓慢的终端或文件系统,因为事件循环被阻塞的足够频繁且足够长的时间,这些可能会给系统性能带来消极的影响。当你向一个交互终端会话写时这可能不是个问题,但当生产日志到进程的输出流时要特别留心。
如何实现一个 console.log? 实现 console.log 在控制台打印,利用 process.stdout 将输入流数据输出到输出流(即输出到终端),一个简单的例子输出 hello world。
process.stdout.write('hello world!' + '\n');为什么 console.log() 执行完后就退出? 这个问题第一次看到是来自于朴灵大神的一次演讲,涉及到 EventLoop 的执行机制,一旦产生事件循环,就会产生一个 While(true) 的死循环,例如定时器 setInterval,但是 console.log 它没有产生 watch、handlers 在事件循环中执行了一次就退出了。同时另一个疑问开启一个 http server 为什么进程没有退出?参考下文章 Node.js 为什么进程没有 exit?。
参考资料
以上就是Console 模块解读及简单实现的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号