JM Shelf - Logger

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

Този скрипт не може да бъде инсталиран директно. Това е библиотека за други скриптове и може да бъде използвана с мета-директива // @require https://update.sleazyfork.org/scripts/581101/1842600/JM%20Shelf%20-%20Logger.js

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==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)}`]);
      }
    };
  })();