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