JM Shelf - Logger

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

Ce script ne doit pas être installé directement. C'est une librairie destinée à être incluse dans d'autres scripts avec la méta-directive // @require https://update.sleazyfork.org/scripts/581101/1842600/JM%20Shelf%20-%20Logger.js

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

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