Greasy Fork

Greasy Fork is available in English.

[DEBUG] 显式日志

用 alert() 提示符合匹配规则的日志或未捕获异常,帮助开发者在日常使用网页时发现潜藏问题

当前为 2021-07-17 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name            [DEBUG] 显式日志
// @version         1.0.0.20210717
// @namespace       laster2800
// @author          Laster2800
// @description     用 alert() 提示符合匹配规则的日志或未捕获异常,帮助开发者在日常使用网页时发现潜藏问题
// @homepage        http://greasyfork.icu/zh-CN/scripts/429521
// @supportURL      http://greasyfork.icu/zh-CN/scripts/429521/feedback
// @license         LGPL-3.0
// @include         *
// @grant           GM_registerMenuCommand
// @grant           GM_setValue
// @grant           GM_getValue
// @grant           unsafeWindow
// @run-at          document-start
// @incompatible    firefox 完全不兼容 Greasemonkey,不完全兼容 Violentmonkey
// ==/UserScript==

(function() {
  'use strict'
  try {
    const gmInclude = GM_getValue('include')
    const gmExclude = GM_getValue('exclude')
    let include = gmInclude ? new RegExp(gmInclude) : null
    let exclude = gmExclude ? new RegExp(gmExclude) : null
    // 日志
    const console = unsafeWindow.console
    const logs = ['log', 'warn', 'error']
    for (const log of logs) {
      const _ = console[log]
      console[log] = function() {
        const m = [arguments, log]
        if (match(m, include) && !match(m, exclude)) {
          explicit(arguments.length == 1 ? arguments[0] : JSON.stringify(arguments), log.toUpperCase())
        }
        return _.apply(console, arguments)
      }
    }
    // 未捕获异常
    unsafeWindow.addEventListener('error', function(event) { // 正常
      const m = [event.error, event.filename, 'Uncaught Exception (Normal)']
      if (match(m, include) && !match(m, exclude)) {
        explicit(event.error, 'Uncaught Exception (Normal)')
      }
    })
    unsafeWindow.addEventListener('unhandledrejection', function(event) { // from Promise
      const m = [event.reason, 'Uncaught Exception (in Promise)']
      if (match(m, include) && !match(m, exclude)) {
        explicit(event.reason, 'Uncaught Exception (in Promise)')
      }
    })
    // 菜单
    GM_registerMenuCommand('设置过滤器', () => {
      try {
        const sInclude = prompt(`【${GM_info.script.name}】\n\n` + '设置匹配过滤器', include?.source ?? '.*')
        include = sInclude ? new RegExp(sInclude) : null
        GM_setValue('include', sInclude ?? '')
        const sExclude = prompt(`【${GM_info.script.name}】\n\n` + '设置排除过滤器', exclude?.source ?? '')
        exclude = sExclude ? new RegExp(sExclude) : null
        GM_setValue('exclude', sExclude ?? '')
      } catch (e) {
        explicit(e)
      }
    })
    GM_registerMenuCommand('说明', () => {
      alert(`【${GM_info.script.name}】\n\n` + '规则\n\n* 正则匹配,不必考虑转义。\n\n* 日志:可用 `LOG` / `WARN` / `ERROR` 作为匹配目标。如用 `^LOG$` 作为排除过滤器排除所有 INFO 级别日志。\n* 日志:无法监听到非直接通过 `console` 对象打印出来的日志,如在油猴黑箱中运行的脚本打印出来的日志。\n\n* 未捕获异常(正常):可用 `Uncaught Exception (Normal)` 作为匹配目标。如简单地用 `cau` 来过滤出所有未捕获异常,但可能混杂带 `cau` 信息的日志。\n* 未捕获异常(正常):可用异常抛出处的文件名作为匹配目标。\n\n* 未捕获异常(Promise):可用 `Uncaught Exception (in Promise)` 作为匹配目标。')
    })
  } catch (e) {
    explicit(e)
  }
})()

/**
 * 显式地显示信息
 * @param {*} msg 信息
 * @param {string} [label] 标记
 */
function explicit(msg, label) {
  alert(`【${GM_info.script.name}】${label ? `【${label}】` : ''}\n\n${msg}`)
}

/**
 * @param {*} obj 匹配对象
 * @param {RegExp} regex 匹配正则表达式
 * @param {number} [depth=5] 匹配查找深度
 * @returns {boolean} 是否匹配成功
 */
function match(obj, regex, depth = 5) {
  if (obj && regex && depth > 0) {
    return core(obj, depth, new Set())
  } else {
    return false
  }

  function core(obj, depth, objSet) {
    for (var key in obj) {
      if (regex.test(key)) {
        return true
      } else {
        try {
          var value = obj[key]
          if (value) {
            if (typeof value == 'object' || typeof value == 'function') {
              if (regex.test(value.toString())) {
                return true
              } else if (depth > 1) {
                if (!objSet.has(value)) {
                  objSet.add(value)
                  if (core(value, depth - 1)) {
                    return true
                  }
                }
              }
            } else {
              if (regex.test(String(value))) {
                return true
              }
            }
          }
        } catch (e) {
          // value that cannot be accessed
        }
      }
    }
    return false
  }
}