JM Shelf - Logger

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

Este script não deve ser instalado diretamente. Este script é uma biblioteca de outros scripts para incluir com o diretório meta // @require https://update.sleazyfork.org/scripts/581101/1842600/JM%20Shelf%20-%20Logger.js

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

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