mirror of
https://github.com/liriliri/eruda.git
synced 2026-03-24 09:48:37 +08:00
487 lines
10 KiB
JavaScript
487 lines
10 KiB
JavaScript
import Log from './Log'
|
|
import {
|
|
Emitter,
|
|
evalCss,
|
|
isNum,
|
|
isUndef,
|
|
perfNow,
|
|
isStr,
|
|
extend,
|
|
uniqId,
|
|
isRegExp,
|
|
isFn,
|
|
stripHtmlTag,
|
|
$,
|
|
Stack,
|
|
isEmpty,
|
|
contain,
|
|
copy,
|
|
each,
|
|
toArr,
|
|
keys
|
|
} from '../lib/util'
|
|
|
|
export default class Logger extends Emitter {
|
|
constructor($el) {
|
|
super()
|
|
this._style = evalCss(require('./Logger.scss'))
|
|
|
|
this._$el = $el
|
|
this._logs = []
|
|
this._timer = {}
|
|
this._count = {}
|
|
this._lastLog = {}
|
|
this._filter = 'all'
|
|
this._maxNum = 'infinite'
|
|
this._displayHeader = false
|
|
this._groupStack = new Stack()
|
|
|
|
// https://developers.google.cn/web/tools/chrome-devtools/console/utilities
|
|
this._global = {
|
|
copy(value) {
|
|
if (!isStr(value)) value = JSON.stringify(value, null, 2)
|
|
copy(value)
|
|
},
|
|
$() {
|
|
return document.querySelector.apply(document, arguments)
|
|
},
|
|
$$() {
|
|
return toArr(document.querySelectorAll.apply(document, arguments))
|
|
},
|
|
clear: () => {
|
|
this.clear()
|
|
},
|
|
dir: value => {
|
|
this.dir(value)
|
|
},
|
|
table: (data, columns) => {
|
|
this.table(data, columns)
|
|
},
|
|
keys
|
|
}
|
|
|
|
this._bindEvent()
|
|
}
|
|
setGlobal(name, val) {
|
|
this._global[name] = val
|
|
}
|
|
displayHeader(flag) {
|
|
this._displayHeader = flag
|
|
}
|
|
maxNum(val) {
|
|
const logs = this._logs
|
|
|
|
this._maxNum = val
|
|
if (isNum(val) && logs.length > val) {
|
|
this._logs = logs.slice(logs.length - val)
|
|
this.render()
|
|
}
|
|
}
|
|
displayUnenumerable(flag) {
|
|
Log.showUnenumerable = flag
|
|
}
|
|
displayGetterVal(flag) {
|
|
Log.showGetterVal = flag
|
|
}
|
|
lazyEvaluation(flag) {
|
|
Log.lazyEvaluation = flag
|
|
}
|
|
viewLogInSources(flag) {
|
|
Log.showSrcInSources = flag
|
|
}
|
|
destroy() {
|
|
evalCss.remove(this._style)
|
|
}
|
|
filter(val) {
|
|
this._filter = val
|
|
this.emit('filter', val)
|
|
|
|
return this.render()
|
|
}
|
|
count(label = 'default') {
|
|
const count = this._count
|
|
|
|
!isUndef(count[label]) ? count[label]++ : (count[label] = 1)
|
|
|
|
return this.info(`${label}: ${count[label]}`)
|
|
}
|
|
countReset(label = 'default') {
|
|
this._count[label] = 0
|
|
|
|
return this
|
|
}
|
|
assert(...args) {
|
|
if (isEmpty(args)) return
|
|
|
|
const exp = args.shift()
|
|
|
|
if (!exp) {
|
|
if (args.length === 0) args.unshift('console.assert')
|
|
args.unshift('Assertion failed: ')
|
|
return this.insert('error', args)
|
|
}
|
|
}
|
|
log(...args) {
|
|
if (isEmpty(args)) return
|
|
|
|
return this.insert('log', args)
|
|
}
|
|
debug(...args) {
|
|
if (isEmpty(args)) return
|
|
|
|
return this.insert('debug', args)
|
|
}
|
|
dir(obj) {
|
|
if (isUndef(obj)) return
|
|
|
|
return this.insert('dir', [obj])
|
|
}
|
|
table(...args) {
|
|
if (isEmpty(args)) return
|
|
|
|
return this.insert('table', args)
|
|
}
|
|
time(name = 'default') {
|
|
if (this._timer[name]) {
|
|
return this.insert('warn', [`Timer '${name}' already exists`])
|
|
}
|
|
this._timer[name] = perfNow()
|
|
|
|
return this
|
|
}
|
|
timeLog(name = 'default') {
|
|
const startTime = this._timer[name]
|
|
|
|
if (!startTime) {
|
|
return this.insert('warn', [`Timer '${name}' does not exist`])
|
|
}
|
|
|
|
return this.info(`${name}: ${perfNow() - startTime}ms`)
|
|
}
|
|
timeEnd(name = 'default') {
|
|
this.timeLog(name)
|
|
|
|
delete this._timer[name]
|
|
|
|
return this
|
|
}
|
|
clear() {
|
|
this.silentClear()
|
|
|
|
return this.insert('log', [
|
|
'%cConsole was cleared',
|
|
'color:#808080;font-style:italic;'
|
|
])
|
|
}
|
|
silentClear() {
|
|
this._logs = []
|
|
this._lastLog = {}
|
|
this._count = {}
|
|
this._timer = {}
|
|
this._groupStack = new Stack()
|
|
|
|
return this.render()
|
|
}
|
|
info(...args) {
|
|
if (isEmpty(args)) return
|
|
|
|
return this.insert('info', args)
|
|
}
|
|
error(...args) {
|
|
if (isEmpty(args)) return
|
|
|
|
return this.insert('error', args)
|
|
}
|
|
warn(...args) {
|
|
if (isEmpty(args)) return
|
|
|
|
return this.insert('warn', args)
|
|
}
|
|
group(...args) {
|
|
return this.insert({
|
|
type: 'group',
|
|
args,
|
|
ignoreFilter: true
|
|
})
|
|
}
|
|
groupCollapsed(...args) {
|
|
return this.insert({
|
|
type: 'groupCollapsed',
|
|
args,
|
|
ignoreFilter: true
|
|
})
|
|
}
|
|
groupEnd() {
|
|
const lastLog = this._lastLog
|
|
lastLog.groupEnd()
|
|
this._groupStack.pop()
|
|
this._refreshLogUi(lastLog)
|
|
}
|
|
input(jsCode) {
|
|
this.insert({
|
|
type: 'input',
|
|
args: [jsCode],
|
|
ignoreFilter: true
|
|
})
|
|
|
|
try {
|
|
this.output(this._evalJs(jsCode))
|
|
} catch (e) {
|
|
this.insert({
|
|
type: 'error',
|
|
ignoreFilter: true,
|
|
args: [e]
|
|
})
|
|
}
|
|
|
|
return this
|
|
}
|
|
output(val) {
|
|
return this.insert({
|
|
type: 'output',
|
|
args: [val],
|
|
ignoreFilter: true
|
|
})
|
|
}
|
|
html(...args) {
|
|
return this.insert('html', args)
|
|
}
|
|
render() {
|
|
let html = ''
|
|
let logs = this._logs
|
|
|
|
logs = this._filterLogs(logs)
|
|
|
|
for (let i = 0, len = logs.length; i < len; i++) {
|
|
html += logs[i].content()
|
|
}
|
|
|
|
this._$el.html(html)
|
|
this.scrollToBottom()
|
|
|
|
return this
|
|
}
|
|
insert(type, args) {
|
|
const logs = this._logs
|
|
const $el = this._$el
|
|
const groupStack = this._groupStack
|
|
const el = $el.get(0)
|
|
|
|
const isAtBottom = el.scrollTop === el.scrollHeight - el.offsetHeight
|
|
|
|
const options = isStr(type) ? { type, args } : type
|
|
if (groupStack.size > 0) {
|
|
options.group = groupStack.peek()
|
|
}
|
|
extend(options, {
|
|
id: uniqId('log'),
|
|
displayHeader: this._displayHeader
|
|
})
|
|
|
|
if (options.type === 'group' || options.type === 'groupCollapsed') {
|
|
const group = {
|
|
id: uniqId('group'),
|
|
collapsed: false,
|
|
parent: groupStack.peek(),
|
|
indentLevel: groupStack.size + 1
|
|
}
|
|
if (options.type === 'groupCollapsed') group.collapsed = true
|
|
options.targetGroup = group
|
|
groupStack.push(group)
|
|
}
|
|
|
|
let log = new Log(options)
|
|
|
|
const lastLog = this._lastLog
|
|
if (
|
|
!contain(['html', 'group', 'groupCollapsed'], log.type) &&
|
|
lastLog.type === log.type &&
|
|
lastLog.value === log.value &&
|
|
!log.src &&
|
|
!log.args
|
|
) {
|
|
lastLog.addCount()
|
|
if (log.time) lastLog.updateTime(log.time)
|
|
log = lastLog
|
|
this._rmLogUi(lastLog)
|
|
} else {
|
|
logs.push(log)
|
|
this._lastLog = log
|
|
}
|
|
|
|
if (this._maxNum !== 'infinite' && logs.length > this._maxNum) {
|
|
const firstLog = logs[0]
|
|
this._rmLogUi(firstLog)
|
|
logs.shift()
|
|
}
|
|
|
|
if (this._filterLog(log)) {
|
|
$el.append(log.content())
|
|
}
|
|
|
|
this.emit('insert', log)
|
|
|
|
if (isAtBottom) this.scrollToBottom()
|
|
|
|
return this
|
|
}
|
|
scrollToBottom() {
|
|
const el = this._$el.get(0)
|
|
|
|
el.scrollTop = el.scrollHeight - el.offsetHeight
|
|
}
|
|
toggleGroup(log) {
|
|
const { targetGroup } = log
|
|
targetGroup.collapsed ? this._openGroup(log) : this._collapseGroup(log)
|
|
}
|
|
_injectGlobal() {
|
|
each(this._global, (val, name) => {
|
|
if (window[name]) return
|
|
|
|
window[name] = val
|
|
})
|
|
}
|
|
_clearGlobal() {
|
|
each(this._global, (val, name) => {
|
|
if (window[name] && window[name] === val) {
|
|
delete window[name]
|
|
}
|
|
})
|
|
}
|
|
_evalJs(jsInput) {
|
|
let ret
|
|
|
|
this._injectGlobal()
|
|
try {
|
|
ret = eval.call(window, `(${jsInput})`)
|
|
} catch (e) {
|
|
ret = eval.call(window, jsInput)
|
|
}
|
|
this.setGlobal('$_', ret)
|
|
this._clearGlobal()
|
|
|
|
return ret
|
|
}
|
|
_rmLogUi(log) {
|
|
const $container = this._$el.find(`li[data-id="${log.id}"]`)
|
|
if ($container.length > 0) {
|
|
$container.remove()
|
|
}
|
|
}
|
|
_refreshLogUi(log) {
|
|
const $container = this._$el.find(`li[data-id="${log.id}"]`)
|
|
if ($container.length > 0) {
|
|
$container.after(log.content())
|
|
$container.remove()
|
|
}
|
|
}
|
|
_filterLogs(logs) {
|
|
const filter = this._filter
|
|
|
|
if (filter === 'all') return logs
|
|
|
|
const isFilterRegExp = isRegExp(filter)
|
|
const isFilterFn = isFn(filter)
|
|
|
|
return logs.filter(log => {
|
|
if (log.ignoreFilter) return true
|
|
if (isFilterFn) return filter(log)
|
|
if (isFilterRegExp) return filter.test(stripHtmlTag(log.content()))
|
|
return log.type === filter
|
|
})
|
|
}
|
|
_filterLog(log) {
|
|
const filter = this._filter
|
|
|
|
if (filter === 'all') return true
|
|
|
|
const isFilterRegExp = isRegExp(filter)
|
|
const isFilterFn = isFn(filter)
|
|
|
|
if (log.ignoreFilter) return true
|
|
if (isFilterFn) return filter(log)
|
|
if (isFilterRegExp) return filter.test(stripHtmlTag(log.content()))
|
|
|
|
return log.type === filter
|
|
}
|
|
_getLog(id) {
|
|
const logs = this._logs
|
|
let log
|
|
|
|
for (let i = 0, len = logs.length; i < len; i++) {
|
|
log = logs[i]
|
|
if (log.id === id) break
|
|
}
|
|
|
|
return log
|
|
}
|
|
_collapseGroup(log) {
|
|
const { targetGroup } = log
|
|
targetGroup.collapsed = true
|
|
log.updateIcon('caret-right')
|
|
this._refreshLogUi(log)
|
|
|
|
this._updateGroup(log)
|
|
}
|
|
_openGroup(log) {
|
|
const { targetGroup } = log
|
|
targetGroup.collapsed = false
|
|
log.updateIcon('caret-down')
|
|
this._refreshLogUi(log)
|
|
|
|
this._updateGroup(log)
|
|
}
|
|
_updateGroup(log) {
|
|
const { targetGroup } = log
|
|
const logs = this._logs
|
|
const len = logs.length
|
|
let i = logs.indexOf(log) + 1
|
|
while (i < len) {
|
|
const log = logs[i]
|
|
if (log.checkGroup()) {
|
|
this._refreshLogUi(log)
|
|
} else if (log.group === targetGroup) {
|
|
break
|
|
}
|
|
i++
|
|
}
|
|
}
|
|
_bindEvent() {
|
|
const self = this
|
|
const $el = this._$el
|
|
|
|
$el
|
|
.on('click', '.eruda-log-container', function() {
|
|
const $el = $(this)
|
|
const id = $el.data('id')
|
|
const type = $el.data('type')
|
|
const log = self._getLog(id)
|
|
if (!log) return
|
|
|
|
Log.click(type, log, $el, self)
|
|
})
|
|
.on('click', '.eruda-icon-caret-down', function() {
|
|
const $el = $(this)
|
|
.parent()
|
|
.parent()
|
|
.parent()
|
|
const id = $el.data('id')
|
|
const log = self._getLog(id)
|
|
if (!log) return
|
|
|
|
self._collapseGroup(log)
|
|
})
|
|
.on('click', '.eruda-icon-caret-right', function() {
|
|
const $el = $(this)
|
|
.parent()
|
|
.parent()
|
|
.parent()
|
|
const id = $el.data('id')
|
|
const log = self._getLog(id)
|
|
if (!log) return
|
|
|
|
self._openGroup(log)
|
|
})
|
|
}
|
|
}
|