Console 模块解读及简单实现

絕刀狂花
发布: 2025-07-31 08:22:28
原创
777人浏览过

console 模块解读及简单实现console 模块提供了简单的调试功能,这在一些测试调试中常是最方便且使用最多的工具。它类似于浏览器中的 console,但有一个关键区别:在浏览器中它是同步的,而在 node.js 中,它是否同步或异步取决于操作系统和输出流类型。

本文参考了官方源码,实现了一个简化的 Console 模块版本,介绍了其基本用法,并提出了常见的问题,供大家学习和参考。具体内容请详见下文。

作者简介:五月君,Node.js 开发者,热爱技术并乐于分享的 90 后青年,公众号「Nodejs技术栈」的作者,GitHub 开源项目维护者,网址为 https://www.php.cn/link/d2450555f921f45a0c0bf6f41bd95a3d

快速导航

  • Logger 模块实现
    • 实现步骤
    • 创建 logger.js 文件
  • Logger 模块基本使用
    • 日志输出至终端
      • log、info、error、warn、clear
    • 日志输出至文件
    • trace 打印错误堆栈
    • dir 显示一个对象的所有属性和方法
    • time 和 timeEnd 计算程序执行消耗时间
  • 面试指南
    • console 是同步的还是异步的?,参考:#
    • 如何实现一个 console.log?,参考:#
    • 为什么 console.log() 执行完后就退出?,参考:#

Logger 模块实现步骤

  • 初始化 Logger 对象
  • 对参数进行检验,当前对象是否为 Logger 实例,是否为一个可写流实例
  • 为 Logger 对象定义 stdout,stderr 等属性
  • 将原型方法上的属性绑定到 Logger 实例上
  • 实现 log、error、warning、trace、clear 等方法

创建 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:

注意: 同步写将会阻塞事件循环直到写完成。有时可能一瞬间就能写到一个文件,但当系统处于高负载时,管道的接收端可能不会被读取、缓慢的终端或文件系统,因为事件循环被阻塞的足够频繁且足够长的时间,这些可能会给系统性能带来消极的影响。当你向一个交互终端会话写时这可能不是个问题,但当生产日志到进程的输出流时要特别留心。

  • 文件(Files): Windows 和 POSIX 平台下都是同步
  • 终端(TTYs): 在 Windows 平台下同步,在 POSIX 平台下异步
  • 管道(Pipes): 在 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中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号