@kai-peng/log
Version:
在您的终端上以样式显示日志消息:-)。使用调试级别启用或禁用日志消息。
737 lines (648 loc) • 24.3 kB
JavaScript
'use strict';
const ch = require('chalk');
const Color = require('color-regex');
//Default text color for bgColors
const defaultBgTextColor = 'black';
const defaultSymbols = {
trace: '??',
success: '√',
ok: '√',
debug: 'i',
info: 'i',
warning: '‼',
warn: '‼',
error: '×'
}
const terminalColors = {
trace: '#3C9DFF',
trace_: '#3C9DFF',
success: '#26FF5C',
success_: '#26FF5C',
ok: '#26FF5C',
ok_: '#26FF5C',
debug: '#34DADA',
debug_: '#34DADA',
info: '#3C9DFF',
info_: '#3C9DFF',
warning: '#FFC926',
warning_: '#FFC926',
warn: '#FFC926',
warn_: '#FFC926',
error: '#F55256',
error_: '#F55256',
}
const browserColors = {
trace: '#3C9DFF',
trace_: '#3C9DFF',
success: '#008000',
success_: '#5AFB78',
ok: '#008000',
ok_: '#5AFB78',
debug: '#07AABC',
debug_: '#80FCFF',
info: '#0151B1',
info_: '#80C6FF',
warning: '#BB8404',
warning_: '#FFD280',
warn: '#BB8404',
warn_: '#FFD280',
error: '#E60000',
error_: '#F55256',
}
const LOG_TEXT = 'TEXT';
const LOG_BG = 'BG';
class LogBeautify {
constructor() {
this.useSymbols = true;
this.useLabels = false;
this.defaultSymbol = '!';
this.defaultLevel = 1;//default level for new log methods
this._localLevels = {};//key=callerFile, value=level
this._namespaceLevels = {};//key=namespace, value=level
this._namespaces = {};//key=callerFile, value=namespace
this._globalLevel = 1;//global level
this._stackTracer = new StackTracer();
this._levels = {
silent: -1,//hide all logs
trace: 0,
success: 1,
ok: 1,
debug: 2,
info: 3,
warning: 4,
warn: 4,
error: 5,
}
this._colors = this._isBrowser() ? browserColors : terminalColors;
//Text colors for bg logs
this._bgTextColors = {
trace_: 'black',
success_: 'black',
ok_: 'black',
debug_: 'black',
info_: 'black',
warning_: 'black',
warn_: 'black',
error_: 'white',
}
this._symbols = this._useUnicodeSymbols() ? {
trace: '⁇',
success: '✔',
ok: '✔',
debug: 'ℹ',
info: '🛈',
warning: '⚠',
warn: '⚠',
error: '✖'
} : defaultSymbols;
this._labels = {
trace: 'TRACE',
success: 'SUCCESS',
ok: 'OK',
debug: 'DEBUG',
info: 'INFO',
warning: 'WARNING',
warn: 'WARNING',
error: 'ERROR'
};
//Create initial logs
this._createLogs();
}
/**
|--------------------------------------------------
| Public methods
|--------------------------------------------------
*/
show(...args){
if (this._shouldLog(this.getWorkingLevel())) {
console.log(...args);
}
}
setLevel(level, namespace = 'global') {
const newLevel = this._parseLevel(level) !== null ? this._parseLevel(level) : this._globalLevel;
if (namespace === 'global') {
this._globalLevel = newLevel;
} else {
this.setNamespaceLevel(newLevel, namespace);
}
}
setLocalLevel(level) {
const newLevel = this._parseLevel(level);
if (newLevel !== null && this._callerFile()) {
this._localLevels[this._callerFile()] = newLevel;
}
}
setNamespaceLevel(level, namespace = null) {
const newLevel = this._parseLevel(level);
if (newLevel !== null && namespace !== null) {
this._namespaceLevels[namespace] = newLevel;
this.useNamespace(namespace);
}
}
useNamespace(namespace = null) {
if (namespace !== null && this._callerFile()) {
this._namespaces[this._callerFile()] = namespace;
}
}
namespace(namespace = null) {
this.useNamespace(namespace);
}
getWorkingLevel() {
const filepath = this._callerFile();
//Check local level
if (filepath && this._localLevels[filepath] !== undefined) {
return this._localLevels[filepath];
}
//Check namespace level
const namespace = this._namespaces[filepath];
if (namespace !== undefined && this._namespaceLevels[namespace] !== undefined) {
return this._namespaceLevels[namespace];
}
//Global level
return this._globalLevel;
}
setLevels(levels) {
Object.keys(levels).map((key) => {
const level = this._parseLevel(levels[key]);
if (level !== null) {
this._levels[key] = level;
}
return key;
});
}
setColors(colors) {
this._colors = { ...this._colors, ...colors };
this._createLogs();
}
setTextColors(bgTextColors) {
this._bgTextColors = { ...this._bgTextColors, ...bgTextColors };
}
setSymbols(symbols) {
this._symbols = { ...this._symbols, ...symbols };
}
setLabels(labels) {
this._labels = { ...this._labels, ...labels };
}
/**
|--------------------------------------------------
| Private methods
|--------------------------------------------------
*/
_useUnicodeSymbols() {
const env = process.env;
return this._isBrowser() || process.platform !== 'win32' || env.CI || env.TERM || env.TERM_PROGRAM;
}
_createLogs() {
Object.keys(this._colors).map((key) => {
if (key.lastIndexOf('_') === key.length - 1) {
this._createBgLog(key);
} else {
this._createTextLog(key);
}
return key;
});
}
_getSymbol(key) {
return this._symbols[key] || this._symbols[this._getKeyName(key)] || this.defaultSymbol;
}
_getLabel(key) {
return this._labels[key] || this._labels[this._getKeyName(key)] || '';
}
_createTextLog(key) {
if (!LogBeautify.prototype[key]) {
LogBeautify.prototype[key] = function (...args) {
this._log(LOG_TEXT, key, args);
}
}
}
_createBgLog(key) {
if (!LogBeautify.prototype[key]) {
LogBeautify.prototype[key] = function (...args) {
this._log(LOG_BG, key, args);
}
}
}
_log(type, key, args) {
const level = this._getKeyLevel(key);
if (this._shouldLog(level)) {
if (this._getKeyName(key) === 'trace') {
console.trace(...args);
} else {
args = this._parseArgs(args);
if (this._isBrowser()) {
this._logBrowser(type, key, args);
} else {
this._logTerminal(type, key, args);
}
}
}
}
_logBrowser(type, key, args) {
if (!isArray(args)) {
console.log(args);
return;
}
let cssPrefix;
let prefix = this._getPrefix(type, key);
if (type === LOG_TEXT) {
cssPrefix = this._getCSS(LOG_BG, key + '_');
} else {
cssPrefix = this._getCSS(LOG_TEXT, this._getKeyName(key));
}
if (prefix) {
if (!this.useLabels) {
args.unshift(this._getSymbolString(key, type === LOG_TEXT ? '' : ' ', ''));
console.log(this._formatLogBrowser(key, args), this._getCSS(type, key), ...args);
} else {
console.log('%c%s' + this._formatLogBrowser(key, args), cssPrefix, prefix, this._getCSS(type, key), ...args);
}
} else {
console.log(this._formatLogBrowser(key, args), this._getCSS(type, key), ...args);
}
}
_logTerminal(type, key, args) {
let text, bg, output;
if (type === LOG_TEXT) {
if (this._getPrefix(type, key) && !this.useLabels) {
args.unshift(this._getSymbolString(key, '', ''));
}
text = this._chTextColor(this._colors[key])(...this._fixArgs(args, key));
output = text;
if (this._getPrefix(type, key)) {
if (!this.useLabels) {
console.log(output);
} else {
console.log(this._getPrefix(type, key), output);
}
} else {
console.log(output);
}
}
if (type === LOG_BG) {
text = this._chTextColor(this._bgTextColors[key] || defaultBgTextColor);
bg = this._chBgColor(this._colors[key]);
if (this._getPrefix(type, key) && !this.useLabels) {
args.unshift(this._getSymbolString(key, ' ', ''));
}
output = bg(text(...this._fixArgs(args, key)));
if (this._getPrefix(type, key)) {
if (!this.useLabels) {
console.log(output);
} else {
console.log(this._getPrefix(type, key), output);
}
} else {
console.log(output);
}
}
}
_getPrefix(type, key) {
let result = this._getSymbolAndLabel(type, key);
if (result.success) {
return result.payload;
}
return '';
}
_getSymbolString(key, before = ' ', after = ' ') {
if (this.useSymbols && this._getSymbol(key)) {
return before + this._getSymbol(key) + after;
}
return '';
}
_getLabelString(key, before = ' ', after = ' ') {
if (this.useLabels && this._getLabel(key)) {
return before + this._getLabel(key) + after;
}
return '';
}
_getSymbolAndLabel(type, key) {
let payload = '';
let symbol = this._getSymbolString(key, ' ', ' ');
let label = symbol ? this._getLabelString(key, '') : this._getLabelString(key);
if (!symbol && !label) {
return {
success: false,
payload
};
}
if (this._isBrowser()) {
payload = symbol + label;
} else {
if (type === LOG_TEXT) {
const text = this._chTextColor(this._bgTextColors[key + '_'] || defaultBgTextColor);
const bg = this._chBgColor(this._colors[key + '_']);
if ( key === 'info' && symbol == " 🛈 " ){
payload = bg(text(symbol + label + ' '));
} else {
payload = bg(text(symbol + label));
}
}
if (type === LOG_BG) {
payload = this._chTextColor(this._colors[this._getKeyName(key)])(symbol + label);
}
}
return {
success: true,
payload
};
}
_fixArgs(args, key) {
let newArgs = [];
if (key === 'info' || key === 'info_' ){
args.push('');
}
args.map(arg => {
if (isArray(arg)) {
newArgs.push('[');
newArgs.push(arg);
newArgs.push(']');
} else {
newArgs.push(arg);
}
return arg;
});
return newArgs;
}
_formatLogBrowser(key, args) {
let label = this._getLabelString(key);
let formater = label ? '%c ' : '%c';
args.map(arg => {
if (typeof arg === 'string') {
formater += '%s ';
} else if (typeof arg === 'number') {
formater += '%d ';
} else if (typeof arg === 'object') {
formater += '%o';//no add spaces for objects and arrays
} else {
formater += '%s ';
}
return arg;
});
return formater.trim();
}
_getCSS(type, key) {
let css = '', color, bgColor;
if (type === LOG_TEXT) {
color = this._colors[key];
css += 'color: ' + color;
}
if (type === LOG_BG) {
color = this._bgTextColors[key] || defaultBgTextColor;
bgColor = this._colors[key];
css += 'color: ' + color;
css += '; background-color: ' + bgColor;
}
return css;
}
_shouldLog(level) {
const workingLevel = this.getWorkingLevel();
const result = workingLevel > this._levels.silent && level >= workingLevel;
if (process.env && process.env.NODE_ENV) {
if (process.env.NODE_ENV === 'dev' || process.env.NODE_ENV === 'development') {
return result;
} else {
//hide all logs on production
return false;
}
}
return result;
}
_getKeyLevel(key) {
const _key = this._getKeyName(key);
//allow level 0
return this._levels[_key] !== undefined ? this._levels[_key] : this.defaultLevel;
}
_getKeyName(key) {
return key.lastIndexOf('_') === key.length - 1 ? key.substring(0, key.length - 1) : key;
}
_chBase() {
//return ch.bold.underline;//bold resets the color
return ch;
}
_chTextColor(_color) {
const format = Color.getFormat(_color);
switch (format) {
case 'hex':
return this._chBase().hex(_color);
case 'rgb':
return this._chBase().rgb(...Color.toArray(_color, true));
case 'hsl':
return this._chBase().hsl(...Color.toArray(_color, true));
case 'hsv':
return this._chBase().hsv(...Color.toArray(_color, true));
case 'hwb':
return this._chBase().hwb(...Color.toArray(_color, true));
default:
return validKeywords.indexOf(_color) >= 0 ? this._chBase().keyword(_color) : this._chBase();
}
}
_chBgColor(_color) {
const format = Color.getFormat(_color);
switch (format) {
case 'hex':
return ch.bgHex(_color);
case 'rgb':
return ch.bgRgb(...Color.toArray(_color, true));
case 'hsl':
return ch.bgHsl(...Color.toArray(_color, true));
case 'hsv':
return ch.bgHsv(...Color.toArray(_color, true));
case 'hwb':
return ch.bgHwb(...Color.toArray(_color, true));
default:
return validKeywords.indexOf(_color) >= 0 ? ch.bgKeyword(_color) : ch;
}
}
_parseLevel(level) {
if (!isNaN(parseInt(level)) && level >= this._levels.silent) {
return parseInt(level);
} else if (typeof level === "string" && this._levels[level.toLowerCase()] !== undefined) {
return this._levels[level.toLowerCase()];
}
return null;
}
_callerFile() {
const traceInfo = this._stackTracer.getInfo();
if (traceInfo.callerFile) {
//return toBase64(traceInfo.callerFile);
return traceInfo.callerFile;
}
return null;
}
_isBrowser() {
return typeof window !== 'undefined' && typeof window.document !== 'undefined';
}
//Chalk not allow print javascript objects
_parseArgs(args) {
return args.map((_arg) => {
if (this._isBrowser()) {
return _arg;
} else {
if (isObject(_arg)) {
return JSON.stringify(_arg, null, 3);
} else if (isArray(_arg)) {
return this._parseArgs(_arg);
}
return _arg;
}
});
}
}
/**
|--------------------------------------------------
| Helpers
|--------------------------------------------------
*/
const isArray = (obj) => {
return !!obj && obj.constructor === Array;
}
const isObject = (obj) => {
return !!obj && obj.constructor === Object;
}
// const toBase64 = (string) => {
// return typeof btoa === "function" ? btoa(string) : Buffer.from(string).toString('base64');
// }
/**
|--------------------------------------------------
| Stack Tracer
|--------------------------------------------------
*/
class StackTracer {
constructor() {
this._isVue = false;
this.traces = {
webpack: [
//React or Vue: Outside of Class component or Function component
'__webpack_require__',
],
react: [
//Class component
'constructClassInstance',
'finishClassComponent',
'commitLifeCycles',
//Function component
'mountIndeterminateComponent',
'updateFunctionComponent'
]
}
}
isNode() {
return typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
}
isBrowser() {
return typeof window !== 'undefined' && typeof window.document !== 'undefined';
}
isReact() {
try {
let arr = window.webpackJsonp[0][0];
return !!arr;
} catch (err) {
return false;
}
}
isVue(trace) {
if (trace.indexOf('Module') !== -1 && trace.indexOf('/vue-loader/') !== -1) {
this._isVue = true;
}
return this._isVue;
}
getReactImports() {
let imports = [];
try {
for (let key in window.webpackJsonp[1][1]) {
imports.push(key);
}
} catch (err) {
//console.log('Log Beautify Error: ', err.message);
}
return imports;
}
getInfo() {
// Save original Error.prepareStackTrace
let origPrepareStackTrace = Error.prepareStackTrace;
let trace;
let currentFile, callerFile = null, lineNumber = null, functionName = null;
try {
//Override with function that just returns 'stack'
Error.prepareStackTrace = function (err, stack) {
return stack;
};
//Create a new 'Error', which automatically gets 'stack'
let err = new Error();
//Evaluate 'err.stack', which calls our new 'Error.prepareStackTrace'
var stack = err.stack;
if (this.isNode()) {
currentFile = stack.shift().getFileName();
for (let key in stack) {
if (!stack.hasOwnProperty(key)) continue;
callerFile = stack[key].getFileName();
if (currentFile !== callerFile) {
lineNumber = stack[key].getLineNumber();
functionName = stack[key].getFunctionName();
break;
}
}
} else if (this.isBrowser()) {
for (let key in stack) {
if (!stack.hasOwnProperty(key)) continue;
const value = stack[key].toString();
const stackName = value.split(' ')[0];
//console.log('$$$', value);
if (this.traces.webpack.indexOf(stackName) !== -1 || this.traces.react.indexOf(stackName) !== -1) {
trace = stack[key - 1].toString();
//React.js
if (this.isReact()) {
if (trace.indexOf('Module') !== -1) {
callerFile = trace.split(' ')[0].replace('Module', '');
} else if (trace.indexOf('new') === 0) {
callerFile = trace.split(' ')[1];//new App (http://test.com/js/main.chunk.js:100:21)
} else {
callerFile = trace.split('.')[0];//HeaderComponent.render
}
if (! /\.(js|jsx)$/gi.test(callerFile)) {
const cf = callerFile;
const filepath = this.getReactImports().find(file => {
if (file.indexOf(cf + '.js', file.length - (cf + '.js').length) !== -1) {
return true;
} else if (file.indexOf(cf + '.jsx', file.length - (cf + '.jsx').length) !== -1) {
return true;
}
return false;
});
callerFile = '.' + filepath;
}
}
//Vue.js
else if (this.isVue(trace)) {
if (trace.indexOf('Module') !== -1) {
if (trace.indexOf('/vue-loader/') !== -1 && trace.indexOf('.vue?vue') !== -1) {
const parts = trace.split('.js?!');
if (parts.length) {
callerFile = parts[parts.length - 1].split('?vue')[0]
}
} else {
callerFile = trace.split(' ')[0].replace('Module', '');
}
}
}
//Files
functionName = callerFile;
lineNumber = stack[key - 1].getLineNumber();
break;
}
}
}
} catch (e) {
//console.log('ERROR', e.message)
}
//Restore original 'Error.prepareStackTrace'
Error.prepareStackTrace = origPrepareStackTrace;
return {
callerFile,
lineNumber,
functionName,
};
}
}
const validKeywords = ["aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige", "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown", "burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue", "cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod", "darkgray", "darkgreen", "darkgrey", "darkkhaki", "darkmagenta", "darkolivegreen", "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen", "darkslateblue", "darkslategray", "darkslategrey", "darkturquoise", "darkviolet", "deeppink", "deepskyblue", "dimgray", "dimgrey", "dodgerblue", "firebrick", "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite", "gold", "goldenrod", "gray", "green", "greenyellow", "grey", "honeydew", "hotpink", "indianred", "indigo", "ivory", "khaki", "lavender", "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral", "lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightgrey", "lightpink", "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightslategrey", "lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta", "maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise", "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin", "navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered", "orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue", "purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue", "slateblue", "slategray", "slategrey", "snow", "springgreen", "steelblue", "tan", "teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white", "whitesmoke", "yellow", "yellowgreen"];
module.exports = new LogBeautify();