JM Shelf - Logger

日志系统 + 页面错误拦截 — JM Shelf 推荐脚本的模块库,通过 @require 被主脚本引用。

Skrip ini tidak untuk dipasang secara langsung. Ini adalah pustaka skrip lain untuk disertakan dengan direktif meta // @require https://update.sleazyfork.org/scripts/581101/1842600/JM%20Shelf%20-%20Logger.js

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         JM Shelf - Logger
// @namespace    jmshelf-lib
// @version      1.0.0
// @author       Kesdi
// @description  日志系统 + 页面错误拦截 — JM Shelf 推荐脚本的模块库,通过 @require 被主脚本引用。
// @license      MIT
// ==/UserScript==
// 
// 此文件是 GreasyFork 库(library),不直接安装。
// 请安装主脚本: JM Shelf 给杂鱼的个性化推荐
//

// ═══ [4] LOGGER ═══ — 同时输出控制台 + 持久化log (便于排查)
  // ============================================================
  const LOG = {
    _prefix: '[JM Shelf]',
    _buffer: [],
    _maxBuffer: 500,
    
    _write(level, args) {
      const msg = args.map(a => (typeof a === 'object' ? JSON.stringify(a) : String(a))).join(' ');
      const entry = { t: Date.now(), l: level, m: msg };
      this._buffer.push(entry);
      if (this._buffer.length > this._maxBuffer) this._buffer.shift();
      this._flush();  // 每次写入立即持久化
    },

    /** 从localStorage恢复历史日志 */
    _restore() {
      try {
        const saved = localStorage.getItem('jms_log');
        if (saved) { const arr = JSON.parse(saved); if (Array.isArray(arr)) this._buffer = arr; }
      } catch(e) {}
    },
    
    _flush() {
      try { localStorage.setItem('jms_log', JSON.stringify(this._buffer.slice(-300))); } catch(e) {}
    },
    
    info(...args)  { console.log(this._prefix, ...args); this._write('I', args); },
    warn(...args)  { console.warn(this._prefix, ...args); this._write('W', args); },
    error(...args) { console.error(this._prefix, ...args); this._write('E', args); },
    debug(...args) { console.debug(this._prefix, ...args); this._write('D', args); },
    
    /** 获取所有日志 (供面板显示) */
    getLogs() {
      try {
        const saved = localStorage.getItem('jms_log');
        const savedLogs = saved ? JSON.parse(saved) : [];
        // 合并内存和持久化, 去重
        const all = [...savedLogs, ...this._buffer];
        const seen = new Set();
        return all.filter(e => { const k = e.t + e.m; if (seen.has(k)) return false; seen.add(k); return true; }).slice(-200);
      } catch(e) { return this._buffer.slice(-200); }
    },
    
    /** 清空日志 */
    clearLogs() { this._buffer = []; try { localStorage.removeItem('jms_log'); } catch(e) {} },
  };

  // 拦截页面全局错误和console.error (捕获18comic自身的JS报错)
  (function setupErrorCapture() {
    // 拦截 uncaught errors
    window.addEventListener('error', (e) => {
      const msg = e.message || e.error?.message || 'Unknown error';
      const src = e.filename || e.target?.src || '';
      // 静默站点自身DOM错误
      if (/ReferenceError|is not defined|Can not detect|innerHTML|addZone|MutationObserver.*Node|indexOf is not/i.test(msg)) return;
      if (src.includes('18comic') || src.includes('airav') || src.includes('owl.carousel')) {
        LOG._write('X', [`[页面错误] ${msg.substring(0, 150)} ${src.split('/').pop()}`]);
      }
    });
    // 拦截 unhandled rejections
    window.addEventListener('unhandledrejection', (e) => {
      const msg = e.reason?.message || String(e.reason).substring(0, 150);
      LOG._write('X', [`[Promise错误] ${msg}`]);
    });
    // 拦截 console.error (站点自身的错误输出)
    const origError = console.error.bind(console);
    console.error = function(...args) {
      origError(...args);
      const msg = args.map(a => (a?.stack || a?.message || String(a)).substring(0, 200)).join(' | ');
      // 过滤: 只记录18comic相关, 跳过JM Shelf自己的
      // 静默站点广告脚本错误 (zbafsnu, p2inl81, vi0ygz7 等随机变量名)
      if (msg.match(/ReferenceError|is not defined|Can not detect viewport|owl/i) && !msg.includes('[JM Shelf]')) {
        return;  // 完全静默, 不记录
      }
      if (!msg.includes('[JM Shelf]') && (msg.includes('18comic') || msg.includes('owl') || msg.includes('MutationObserver') || msg.includes('Deferred'))) {
        LOG._write('X', [`[站点错误] ${msg.substring(0, 180)}`]);
      }
    };
  })();