UNPKG

fh-csl

Version:

Beautify console logs

400 lines (374 loc) 9.81 kB
// 初始化日志对象 const csl = { ...console } // 日志队列 const logsQuene = [] // 队列是否正在执行 let logsQueueExecuting = false // 日志索引 let logIndex = 1 // 配置项 const options = { // subject、action、target 的分割符 split: '>', // 主体颜色值 subjectColors: { req: '#aaa', res: '#00aa00', soc: '#ff34b3', bus: '##9932cc', app: '#7fff00', non: '#f00', sys: '#666', }, // 默认主体颜色值 defaultSubjectColor: '#67cdfe', // 日志等级颜色值 levelColors: { log: '#000', info: 'cyan', debug: '#4169E1', warn: 'orange', error: 'red', }, // 忽略规则 ignores: [ ] } // 默认占用 console 对象 setGrab(true) /** * 设置是否占用 console 对象 * @param {Boolean} grab 是否占用 console 对象 */ function setGrab(grab) { Object.keys(options.levelColors).forEach(level => { console[level] = grab ? (function () { print(level, { subject: 'non', action: 'log', target: 'unknown', remark: '未归组日志', extra: [...arguments] }) }) : csl[level] }) } /** * 设置忽略规则 * @param {Array} ignores */ function setIgnores(ignores) { ignores = ignores.map(ig => { const arr = ig.split('>') return { level: arr[0], subject: arr[1], action: arr[2], target: arr[3] } }) options.ignores = ignores } /** * 取消忽略规则 */ function clearIgnores() { options.ignores = [] } /** * 检查是否符合忽略规则 * @param {Object} data */ function isIgnored(data) { const { level, subject, action, target } = data const { ignores } = options return ignores.some(ig => ['*', level].includes(ig.level) && ['*', subject].includes(ig.subject) && ['*', action].includes(ig.action) && ['*', target].includes(ig.target) ) } /** * 添加主体 * @param {Object} colors 主体颜色 */ function setSubjectColors(colors) { Object.assign(options.subjectColors, colors) } /** * 清除主体 */ function clearSubjectColors() { options.subjectColors = {} } /** * 设置默认主体颜色值 * @param {String} color 颜色值 */ function setDefaultSubjectColor(color) { options.defaultSubjectColor = color } /** * 获取日志主体打印颜色 * @param {string} subject */ function getSubjectColor(subject) { return options.subjectColors[subject] || options.defaultSubjectColor } /** * 打印日志 * @param {Object} data * @param {String} data.level 日志等级,与 console 的等级一致 * @param {String} data.subject 触发者 * @param {String} data.action 行为 * @param {String} data.target 作用方 * @param {String} data.meta 行为数据 */ function print() { const data = checkParameter(...arguments) if (!data) return if (isIgnored(data)) return let { level, subject, action, target, remark, extra } = data let date = formatDate(new Date(), 'HH:mm:ss.fff') extra = extra || {} extra = extra instanceof Object ? extra : { extra } logsQuene.push({ level, subject, action, target, remark, extra, date }) setTimeout(queueExecutor) } /** * 打印 log 日志 */ function log() { print('log', ...arguments) } /** * 打印 debug 日志 */ function debug() { print('debug', ...arguments) } /** * 打印 info 日志 */ function info() { print('info', ...arguments) } /** * 打印 warn 日志 */ function warn() { print('warn', ...arguments) } /** * 打印 error 日志 */ function error() { print('error', ...arguments) } /** * 检查日志参数是否正确,并返回日志对象 * @returns 日志对象 */ function checkParameter() { const arg0 = arguments[0] const arg1 = arguments[1] const arg2 = arguments[2] const arg3 = arguments[3] const arg4 = arguments[4] const arg5 = arguments[5] let data = null try { const splitArr = arg1?.toString().split(options.split) switch (arguments.length) { case 2: if (Object.prototype.toString.apply(arg1) === '[object String]') { if (splitArr.length === 3) { data = { level: arg0, subject: splitArr[0], action: splitArr[1], target: splitArr[2] } } } else { data = { level: arg0, ...arg1 } } break case 3: if (splitArr.length === 3) { data = { level: arg0, subject: splitArr[0], action: splitArr[1], target: splitArr[2], remark: arg2 } } break case 4: if (splitArr.length === 3) { data = { level: arg0, subject: splitArr[0], action: splitArr[1], target: splitArr[2], remark: arg2, extra: arg3 } } else if (splitArr.length === 1) { data = { level: arg0, subject: arg1, action: arg2, target: arg3, } } break case 5: data = { level: arg0, subject: arg1, action: arg2, target: arg3, remark: arg4 } break case 6: data = { level: arg0, subject: arg1, action: arg2, target: arg3, remark: arg4, extra: arg5 } break } } catch { } if (!data) return invalidParameter(arguments) if (Object.prototype.toString.apply(data) !== '[object Object]') return invalidParameter(arguments) if (!Object.keys(options.levelColors).includes(data.level)) return invalidParameter(arguments) if (!data.subject || Object.prototype.toString.apply(data.subject) !== '[object String]') return invalidParameter(arguments) if (!data.action || Object.prototype.toString.apply(data.action) !== '[object String]') return invalidParameter(arguments) if (!data.target || Object.prototype.toString.apply(data.target) !== '[object String]') return invalidParameter(arguments) return data } /** * 日志参数错误时的提示 */ function invalidParameter(args) { error('csl>error>print', 'csl日志参数错误', Array.from(arguments[0]).slice(1)) } /** * 队列执行 */ function queueExecutor() { if (logsQueueExecuting || !logsQuene.length) return logsQueueExecuting = true let [{ level, subject, action, target, remark = '', extra = null, date }] = logsQuene if (isDevTools()) { const baseStyle = 'border:1px solid #000;color:#aaa;background:#242424;padding: 2px 4px;border-radius: 4px;' const styles = [ `${baseStyle}margin: 0 2px 0;`, `border:1px solid ${options.levelColors[level]};color:${getSubjectColor(subject)};background:#111;padding: 2px 4px;border-radius: 4px;`, `${baseStyle}margin: 2px`, ] csl.groupCollapsed(`%c${subject}%c${action}%c${target}`, ...styles) csl.groupCollapsed(`%cstack trace%c${date}%c${zeroify(logIndex++, 4)}%c${remark.replace(/\s/g, '')}`, `${baseStyle}margin: 0 2px 0;'`, `${baseStyle}margin: 0 2px 0 0;'`, `${baseStyle}'`, `${baseStyle}margin: 2px;`) csl.trace() csl.groupEnd() csl.log(JSON.stringify(extra, null, 4)) csl.groupEnd() } else { extra = extra instanceof Error ? { extra } : extra const styles = [ `display:inline-block;padding:0 3px;background:${{ error: 'red', warn: 'gold' }[level] || '#aaa'};color:#fff;border-radius:5px;margin:5px 0 0`, 'word-break:break-all;white-space:pre-wrap;', ] csl['log'].apply(csl, [`%c ${date} %c ${subject}>${action}>${target} ${remark ? ' ' + remark : ''}`, ...styles, extra]) } logsQuene.shift() logsQueueExecuting = false setTimeout(queueExecutor) } /** * 将 Date 转化为指定格式的String */ function formatDate(date, fmt) { date = date || new Date() fmt = fmt || 'yyyy-MM-dd HH:mm:ss.fff' // 正则与值的对应关系 let regmap = { 'y+': date.getFullYear(), // 年份 'M+': date.getMonth() + 1, //月份 'd+': date.getDate(), //日 'H+': date.getHours(), //小时 'm+': date.getMinutes(), //分 's+': date.getSeconds(), //秒 'f+': date.getMilliseconds(), // 毫秒 } // 循环替换正则为实际的值 for (let reg in regmap) { if (new RegExp(`(${reg})`).test(fmt)) { fmt = fmt.replace(RegExp.$1, zeroify(regmap[reg], RegExp.$1.length)) } } return fmt } /** * 字符串前补0 * @param {String} str 字符串 * @param {Number} max 最大长度 * @param {Boolean} cut 原字符串超出max时是否裁剪 */ function zeroify(str, max, cut = false) { str = str ? str.toString() : '' if (max < str.length) { if (cut) { str = str.substring(0, max) } } else { while (max - str.length > 0) { str = `0${str}` } } return str } let _isDevTools /** * 当前环境是否是开发环境 */ function isDevTools() { if (_isDevTools === undefined) { if (wx) { let { brand } = wx.getSystemInfoSync() _isDevTools = brand === 'devtools' } else { _isDevTools = true } } return _isDevTools } module.exports = { log, info, debug, warn, error, setGrab, setIgnores, clearIgnores, setSubjectColors, clearSubjectColors, setDefaultSubjectColor }