通过不同的实例打印,查看输出
## node.js
// https://github.com/visionmedia/debug
// util.format: 根据使用第一个参数返回格式化的字符串。格式化的字符串包含零个或多个格式说明符
// util.inspect: 根据第二个参数将对象转换 为字符串的方法
// process.stderr.writ: 控制台打印字符串
/**
* 1. 把 'init', 'log', 'formatArgs', 'save', 'load', 'useColors', 'colors', 'inspectOpts' 挂到exports中,导入'common.js'默认的导出方法'setup'并且调用把exports对象全部当参数传递进去
1. init: 初始化“调试”实例,从环境变量构建默认的' inspectOpts '对象给debug
1. showHidden 是一个可选参数,如果值为 true,将会输出更多隐藏信息
2. colors 值为 true,输出格式将会以 ANSI 颜色编码
3. depth 表示最大递归的层数,如果对象很复杂,你可以指定层数以控制输出信息的多 少。如果不指定depth,默认会递归 2 层
2. log: 打印日志
3. formatArgs:添加ANSI颜色转义码
4. save: 有namespaces就保存到process.env.DEBUG,没有就删除
5. useColors: 判断inspectOpts有没有colors,有就返回false,没有就是true
6. colors: 当前实例的颜色
7. inspectOpts: 获取debug_开头的process.env环境变量,取debug_后面的变量并且转换为驼峰,然后值进行true,false,null等转换后存到debug.inspectOpts中
* 2. setup被调用后给createDebug对象挂载'debug', 'default', 'coerce', 'disable', 'enable', 'enabled', 'humanize', 'inspectOpts', 'selectColor'并且通过循环把exports挂载的属性全部挂载到setup中
1. debug: 默认到处的方法给开发者使用
2. default: createDebug函数本身
3. coerce: 如果传递的参数是报错,转换输出为报错信息,msg返回
4. disable: 禁用调试输出
5. enable: 动态控制调试的启动和关闭
6. enabled: 判断实例是否被开启
7. humanize: ms模块,用于时间转换
8. inspectOpts: 实例的配置,showHidden,colors,depth
9. selectColor: 为这个名称的调试选择颜色
* 3. 走createDebug.enable(createDebug.load()), createDebug.load()获取process.env.DEBUG的值,
1. 调用createDebug.save判断createDebug.load()有没有数据,有数据就存到process.env.DEBUG,没有就删除process.env.DEBUG
2. 如果createDebug.load()不是字符串的话就进行切割并且把*都替换成.*?, 如果第一个字符串是-就删除掉记录到skips中不是的话就记录到names中,names和skips和namespaces挂载到createDebug中
3. return createDebug给开发者调用
* 4. 当开发者const debug = require('debug')('detect-port')后导入了createDebug并且调用传递一个名称'detect-port'
1. debug挂载 ’namespace‘,’useColors‘,’color‘,’extend‘
1. namespace: 当前实例的名称,用来区分每一个debug
2. useColors: createDebug.useColors(),根据inspectOpts判断有没有colors,有就说false没有就算true
3. color: createDebug.selectColor(namespace),为这个名称的调试选择颜色
4. extend: 基于这个实力扩展另外一个debug实例,实例名称
2. 使用Object.defineProperty控制debug的enabled属性,调用debug.enabled的时候自动判断namespaces和缓存的namespaces不一样并且属于createDebug.skips就开启调试,不然就无法开启调试
3. 调用createDebug.init(debug),环境的初始化逻辑
4. return debug提供开发者使用
* 5. debug('detect free port between [%o, %s)', 3000, 65535);
*/
/**
* 1. 可以设置一些环境变量来改变调试日志的行为,以DEBUG_开头的环境变量最终被转换为一个Options对象
1. DEBUG: 启用/禁用特定调试名称空间
2. DEBUG_HIDE_DATE: 从调试输出中隐藏日期(非tty)。
3. DEBUG_COLORS: 是否在调试输出中使用颜色。
4. DEBUG_DEPTH: 对象检查深度。
5. DEBUG_SHOW_HIDDEN: 显示检查对象的隐藏属性。
* 2. 通过%设置参数的替换方法,可以createDebug.formatters.h扩展一个%h的方法
1. %O:多行打印一个对象
2. %o:将一个对象全部打印在一行上
3. %s:打印字符串
4. %d:数字(整数和浮点数)。
5. %j:JSON。如果参数包含循环引用,则用字符串'[Circular]'替换
6. %%:单百分比符号('%')。这不会消耗一个参数。
* 3. 通过extend扩展debug实例
1. const log = require('debug')('auth');
const logSign = log.extend('sign');
const logLogin = log.extend('login');
log('hello'); // auth hello
logSign('hello'); //auth:sign hello
logLogin('hello'); //auth:login hello
* 4. 调用enable()方法动态地启用调试
1. require('debug').enabled('test')
* 5. 将禁用所有名称空间。这些函数返回当前启用(并跳过)的名称空间。如果您想在不知道启用了什么的情况下暂时禁用调试,那么这将非常有用
1. require('debug')('auth').disable()
* 6. 在你创建了一个调试实例之后,你可以通过检查enabled属性来确定它是否被启用,您还可以手动切换此属性,以强制启用或禁用调试实例。
1. require('debug')('http').enabled
* 7.
*/
/**
* 模块依赖
*/
// 终端模块
const tty = require('tty');
// 实用工具
const util = require('util');
/**
* node环境
*/
exports.init = init;
exports.log = log;
exports.formatArgs = formatArgs;
exports.save = save;
exports.load = load;
exports.useColors = useColors;
/**
* Colors.
*/
exports.colors = [6, 2, 3, 4, 5, 1];
try {
// Optional dependency (as in, doesn't need to be installed, NOT like optionalDependencies in package.json)
// eslint-disable-next-line import/no-extraneous-dependencies
const supportsColor = require('supports-color');
if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) {
exports.colors = [
20,
21,
26,
27,
32,
33,
38,
39,
40,
41,
42,
43,
44,
45,
56,
57,
62,
63,
68,
69,
74,
75,
76,
77,
78,
79,
80,
81,
92,
93,
98,
99,
112,
113,
128,
129,
134,
135,
148,
149,
160,
161,
162,
163,
164,
165,
166,
167,
168,
169,
170,
171,
172,
173,
178,
179,
184,
185,
196,
197,
198,
199,
200,
201,
202,
203,
204,
205,
206,
207,
208,
209,
214,
215,
220,
221
];
}
} catch (error) {
// Swallow - we only care if `supports-color` is available; it doesn't have to be.
}
/**
* 从环境变量构建默认的' inspectOpts '对象。
*
* $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js
* showHidden 是一个可选参数,如果值为 true,将会输出更多隐藏信息
* colors 值为 true,输出格式将会以 ANSI 颜色编码
* depth 表示最大递归的层数,如果对象很复杂,你可以指定层数以控制输出信息的多 少。如果不指定depth,默认会递归 2 层
*/
exports.inspectOpts = Object.keys(process.env).filter(key => {
// 过滤找process里面的环境变量debug_开头的并且部分大小写
return /^debug_/i.test(key);
}).reduce((obj, key) => {
// Camel-case
const prop = key
.substring(6) // 去除debug_
.toLowerCase() // 转换为小写
.replace(/_([a-z])/g, (_, k) => { // 匹配下划线,全部切换为大写
return k.toUpperCase();
});
// 强制将字符串值转换为JS值
let val = process.env[key];
if (/^(yes|on|true|enabled)$/i.test(val)) {
val = true;
} else if (/^(no|off|false|disabled)$/i.test(val)) {
val = false;
} else if (val === 'null') {
val = null;
} else {
val = Number(val);
}
obj[prop] = val;
return obj;
}, {});
/**
* 如果启用,则添加ANSI颜色转义码。
*
* @api public
*/
function formatArgs(args) {
const {namespace: name, useColors} = this;
if (useColors) {
const c = this.color;
const colorCode = '\u001B[3' + (c < 8 ? c : '8;5;' + c);
const prefix = ` ${colorCode};1m${name} \u001B[0m`;
args[0] = prefix + args[0].split('\n').join('\n' + prefix);
args.push(colorCode + 'm+' + module.exports.humanize(this.diff) + '\u001B[0m');
} else {
args[0] = getDate() + name + ' ' + args[0];
}
}
function getDate() {
if (exports.inspectOpts.hideDate) {
return '';
}
return new Date().toISOString() + ' ';
}
/**
* 使用指定的参数调用' util.format() '并写入stderr。
* process.stderr.write: 终端输出log
*/
function log(...args) {
return process.stderr.write(util.format(...args) + '\n');
}
/**
* 保存 `namespaces`.
*
* @param {String} namespaces
* @api private
*/
function save(namespaces) {
// 有namespaces设置process.env.DEBUG
if (namespaces) process.env.DEBUG = namespaces;
else delete process.env.DEBUG; //如果你设置了一个进程。如果Env字段为null或未定义,它将被转换为 //字符串'null'或'undefined'。只是删除
}
/**
* 加载 `namespaces`.
*
* @return {String} 返回先前持久化的调试模式
* @api private
*/
function load() {
return process.env.DEBUG;
}
/**
* 初始化“调试”实例的逻辑。
*
* 创建一个新的' inspectOpts '对象,以防' useColors '被设置 *对于特定的' debug '实例是不同的。
*/
function init(debug) {
debug.inspectOpts = {};
// 从环境变量构建默认的' inspectOpts '对象给debug
const keys = Object.keys(exports.inspectOpts);
for (let i = 0; i < keys.length; i++) {
debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]];
}
}
module.exports = require('./common')(exports);
const {formatters} = module.exports;
/**
* 挂载到formatters的方法,用来做调用,有%o的时候就把%o替换为方法返回的值,参数从调用debug第二次参数对应%的index
*/
formatters.o = function (v) {
// 有colors属性就返回false, 没有就返回true
this.inspectOpts.colors = this.useColors;
// 将任意对象转换 为字符串的方法
return util.inspect(v, this.inspectOpts)
.split('\n')
.map(str => str.trim())
.join(' ');
};
/**
* Map %O to `util.inspect()`, allowing multiple lines if needed.
*/
formatters.O = function (v) {
this.inspectOpts.colors = this.useColors;
return util.inspect(v, this.inspectOpts);
};
/**
* stdout是TTY吗?当' true '时启用彩色输出。
* 有colors属性就返回false, 没有就返回true
*/
function useColors() {
return 'colors' in exports.inspectOpts ? // 判断从环境中找的默认配置有没有这个属性
Boolean(exports.inspectOpts.colors) : // 有color就变为false
tty.isatty(process.stderr.fd); // process.stderr.fd固定为2, 然后返回true
}
## common.js
/**
* This is the common logic for both the Node.js and web browser
* implementations of `debug()`.
*/
function setup(env) {
// 给对象绑定方法
createDebug.debug = createDebug;
createDebug.default = createDebug;
createDebug.coerce = coerce;
createDebug.disable = disable;
createDebug.enable = enable;
createDebug.enabled = enabled;
createDebug.humanize = require('ms');
createDebug.selectColor = selectColor;
// 扩展方法
Object.keys(env).forEach(key => {
createDebug[key] = env[key];
});
/**
* 当前激活的调试模式名称,以及要跳过的名称。
*/
createDebug.names = [];
createDebug.skips = [];
/**
* 特殊"%n"处理函数的映射,用于调试"format"参数。
*
* 有效的键名是一个单一的小写或大写字母,即。“n”和“n”。
*/
createDebug.formatters = {};
/**
* 为调试名称空间选择颜色
* @param {String} namespace 要着色的调试实例的名称空间字符串
* @return {Number|String} 给定名称空间的ANSI颜色代码
* @api private
*/
function selectColor(namespace) {
let hash = 0;
for (let i = 0; i < namespace.length; i++) {
hash = ((hash << 5) - hash) + namespace.charCodeAt(i);
hash |= 0; // Convert to 32bit integer
}
return createDebug.colors[Math.abs(hash) % createDebug.colors.length];
}
/**
* 用给定的命名空间创建一个调试器
*
* @param {String} namespace
* @return {Function}
* @api public
*/
function createDebug(namespace) {
let prevTime;
let enableOverride = null;
let namespacesCache;
let enabledCache;
// args[0]举例:detect free port between [%s, %s)
function debug(...args) {
// 检查调试是否被启用?
if (!debug.enabled) {
return;
}
const self = debug;
// 计算调试的时间
const curr = Number(new Date());
const ms = curr - (prevTime || curr);
self.diff = ms;
self.prev = prevTime;
self.curr = curr;
prevTime = curr;
// 转换数据,如果是报错就返回报错信息
args[0] = createDebug.coerce(args[0]);
// 其他类型,就把%O拼到前面
if (typeof args[0] !== 'string') {
args.unshift('%O');
}
// 应用任何'格式化器'转换
let index = 0;
// %: 单纯的一个%符号
// [a-zA-Z%]: 匹配a-z或者A-Z或者%任意一个字符都成立
// g: 全局匹配
// 过滤%开头到a-z A-z %中的字符串,注意是%后的第一个不是全部
args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => {
// 如果遇到了两个%%就替换为%
if (match === '%%') {
return '%';
}
index++;
// 根据%后面的符号调用方法默认有o和O两个方法
const formatter = createDebug.formatters[format];
if (typeof formatter === 'function') {
// 获取后面的参数进行处理
const val = args[index];
match = formatter.call(self, val);
// 现在我们需要删除' args[index] ',因为它被内联在' format '中
args.splice(index, 1);
index--;
}
return match;
});
// 应用特定于环境的格式(颜色等)
createDebug.formatArgs.call(self, args);
const logFn = self.log || createDebug.log;
logFn.apply(self, args);
}
debug.namespace = namespace;
debug.useColors = createDebug.useColors();
debug.color = createDebug.selectColor(namespace);
debug.extend = extend;
// 调用debug.enabled的时候自动判断namespaces和缓存的namespaces不一样并且属于createDebug.skips就开启调试,检查调试是否被启用
Object.defineProperty(debug, 'enabled', {
enumerable: true, // 当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中
configurable: false, // 为 true 时,该属性的描述符才能够被改变
get: () => {
if (enableOverride !== null) {
return enableOverride;
}
if (namespacesCache !== createDebug.namespaces) {
namespacesCache = createDebug.namespaces;
enabledCache = createDebug.enabled(namespace);
}
return enabledCache;
},
set: v => {
enableOverride = v;
}
});
// 用于调试实例的特定于环境的初始化逻辑
if (typeof createDebug.init === 'function') {
createDebug.init(debug);
}
return debug;
}
// 基于之前的namespace扩展一个新的debug实例:之前namespace:传递进来的namespace
function extend(namespace, delimiter) {
const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace);
newDebug.log = this.log;
return newDebug;
}
/**
* 按名称空间启用调试模式。这可以包括模式 *由冒号和通配符分隔
*
* @param {String} namespaces
* @api public
*/
function enable(namespaces) {
createDebug.save(namespaces);
// 存namespaces
createDebug.namespaces = namespaces;
// 存第一个字符不是-开头的正则表达式
createDebug.names = [];
// 存第一个字符是-开头的正则表达式
createDebug.skips = [];
let i;
// []: 匹配里面任意一个规则\s或者,都行,满足其中一个都行
// \s: 匹配一个空字符串,空格,制表符,换页符换行符
// ,: 就当成匹配一个逗号
// +: 前面表达式一个或者多个
// 如果字符串中有空白符号或者,一次或者多次,都进行切割
const split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/);
const len = split.length;
for (i = 0; i < len; i++) {
// 如果是空格,结束当前循环,进行下一个循环
if (!split[i]) continue;
// g: 全局匹配
// /*: 匹配*号,不做正则符号翻译
// 字符串中出现*都替换成.*?
namespaces = split[i].replace(/\*/g, '.*?');
// namespaces[0]: 字符串第一个符号 === ‘-’
if (namespaces[0] === '-') {
// 删除第一个字符串-并且生成一个正则表达式/^ *** $/
createDebug.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));
} else {
// 直接生成正则表达式 /^ *** $/
createDebug.names.push(new RegExp('^' + namespaces + '$'));
}
}
}
/**
* 禁用调试输出
*
* @return {String} namespaces
* @api public
*/
function disable() {
const namespaces = [
...createDebug.names.map(toNamespace),
...createDebug.skips.map(toNamespace).map(namespace => '-' + namespace)
].join(',');
createDebug.enable('');
return namespaces;
}
/**
* 如果启用了给定的模式名,则返回true,否则返回false。
*
* @param {String} name
* @return {Boolean}
* @api public
*/
function enabled(name) {
if (name[name.length - 1] === '*') {
return true;
}
let i;
let len;
for (i = 0, len = createDebug.skips.length; i < len; i++) {
if (createDebug.skips[i].test(name)) {
return false;
}
}
for (i = 0, len = createDebug.names.length; i < len; i++) {
if (createDebug.names[i].test(name)) {
return true;
}
}
return false;
}
/**
* Convert regexp to namespace
*
* @param {RegExp} regxep
* @return {String} namespace
* @api private
*/
function toNamespace(regexp) {
return regexp.toString()
.substring(2, regexp.toString().length - 2)
.replace(/\.\*\?$/, '*');
}
/**
* 如果是报错,转换输出为报错信息
*
* @param {Mixed} val
* @return {Mixed}
* @api private
*/
function coerce(val) {
if (val instanceof Error) {
return val.stack || val.message;
}
return val;
}
// 根据rocess.env.DEBUG初始化数据
// createDebug.load(): 获取process.env.DEBUG
// createDebug.enable: 保存namespaces(rocess.env.DEBUG)以及过滤保存skips,names
createDebug.enable(createDebug.load());
return createDebug;
}
module.exports = setup;