Spaces:
Paused
Paused
| import { $el, ComfyDialog } from "./ui.js"; | |
| import { api } from "./api.js"; | |
| $el("style", { | |
| textContent: ` | |
| .comfy-logging-logs { | |
| display: grid; | |
| color: var(--fg-color); | |
| white-space: pre-wrap; | |
| } | |
| .comfy-logging-log { | |
| display: contents; | |
| } | |
| .comfy-logging-title { | |
| background: var(--tr-even-bg-color); | |
| font-weight: bold; | |
| margin-bottom: 5px; | |
| text-align: center; | |
| } | |
| .comfy-logging-log div { | |
| background: var(--row-bg); | |
| padding: 5px; | |
| } | |
| `, | |
| parent: document.body, | |
| }); | |
| // Stringify function supporting max depth and removal of circular references | |
| // https://stackoverflow.com/a/57193345 | |
| function stringify(val, depth, replacer, space, onGetObjID) { | |
| depth = isNaN(+depth) ? 1 : depth; | |
| var recursMap = new WeakMap(); | |
| function _build(val, depth, o, a, r) { | |
| // (JSON.stringify() has it's own rules, which we respect here by using it for property iteration) | |
| return !val || typeof val != "object" | |
| ? val | |
| : ((r = recursMap.has(val)), | |
| recursMap.set(val, true), | |
| (a = Array.isArray(val)), | |
| r | |
| ? (o = (onGetObjID && onGetObjID(val)) || null) | |
| : JSON.stringify(val, function (k, v) { | |
| if (a || depth > 0) { | |
| if (replacer) v = replacer(k, v); | |
| if (!k) return (a = Array.isArray(v)), (val = v); | |
| !o && (o = a ? [] : {}); | |
| o[k] = _build(v, a ? depth : depth - 1); | |
| } | |
| }), | |
| o === void 0 ? (a ? [] : {}) : o); | |
| } | |
| return JSON.stringify(_build(val, depth), null, space); | |
| } | |
| const jsonReplacer = (k, v, ui) => { | |
| if (v instanceof Array && v.length === 1) { | |
| v = v[0]; | |
| } | |
| if (v instanceof Date) { | |
| v = v.toISOString(); | |
| if (ui) { | |
| v = v.split("T")[1]; | |
| } | |
| } | |
| if (v instanceof Error) { | |
| let err = ""; | |
| if (v.name) err += v.name + "\n"; | |
| if (v.message) err += v.message + "\n"; | |
| if (v.stack) err += v.stack + "\n"; | |
| if (!err) { | |
| err = v.toString(); | |
| } | |
| v = err; | |
| } | |
| return v; | |
| }; | |
| const fileInput = $el("input", { | |
| type: "file", | |
| accept: ".json", | |
| style: { display: "none" }, | |
| parent: document.body, | |
| }); | |
| class ComfyLoggingDialog extends ComfyDialog { | |
| constructor(logging) { | |
| super(); | |
| this.logging = logging; | |
| } | |
| clear() { | |
| this.logging.clear(); | |
| this.show(); | |
| } | |
| export() { | |
| const blob = new Blob([stringify([...this.logging.entries], 20, jsonReplacer, "\t")], { | |
| type: "application/json", | |
| }); | |
| const url = URL.createObjectURL(blob); | |
| const a = $el("a", { | |
| href: url, | |
| download: `comfyui-logs-${Date.now()}.json`, | |
| style: { display: "none" }, | |
| parent: document.body, | |
| }); | |
| a.click(); | |
| setTimeout(function () { | |
| a.remove(); | |
| window.URL.revokeObjectURL(url); | |
| }, 0); | |
| } | |
| import() { | |
| fileInput.onchange = () => { | |
| const reader = new FileReader(); | |
| reader.onload = () => { | |
| fileInput.remove(); | |
| try { | |
| const obj = JSON.parse(reader.result); | |
| if (obj instanceof Array) { | |
| this.show(obj); | |
| } else { | |
| throw new Error("Invalid file selected."); | |
| } | |
| } catch (error) { | |
| alert("Unable to load logs: " + error.message); | |
| } | |
| }; | |
| reader.readAsText(fileInput.files[0]); | |
| }; | |
| fileInput.click(); | |
| } | |
| createButtons() { | |
| return [ | |
| $el("button", { | |
| type: "button", | |
| textContent: "Clear", | |
| onclick: () => this.clear(), | |
| }), | |
| $el("button", { | |
| type: "button", | |
| textContent: "Export logs...", | |
| onclick: () => this.export(), | |
| }), | |
| $el("button", { | |
| type: "button", | |
| textContent: "View exported logs...", | |
| onclick: () => this.import(), | |
| }), | |
| ...super.createButtons(), | |
| ]; | |
| } | |
| getTypeColor(type) { | |
| switch (type) { | |
| case "error": | |
| return "red"; | |
| case "warn": | |
| return "orange"; | |
| case "debug": | |
| return "dodgerblue"; | |
| } | |
| } | |
| show(entries) { | |
| if (!entries) entries = this.logging.entries; | |
| this.element.style.width = "100%"; | |
| const cols = { | |
| source: "Source", | |
| type: "Type", | |
| timestamp: "Timestamp", | |
| message: "Message", | |
| }; | |
| const keys = Object.keys(cols); | |
| const headers = Object.values(cols).map((title) => | |
| $el("div.comfy-logging-title", { | |
| textContent: title, | |
| }) | |
| ); | |
| const rows = entries.map((entry, i) => { | |
| return $el( | |
| "div.comfy-logging-log", | |
| { | |
| $: (el) => el.style.setProperty("--row-bg", `var(--tr-${i % 2 ? "even" : "odd"}-bg-color)`), | |
| }, | |
| keys.map((key) => { | |
| let v = entry[key]; | |
| let color; | |
| if (key === "type") { | |
| color = this.getTypeColor(v); | |
| } else { | |
| v = jsonReplacer(key, v, true); | |
| if (typeof v === "object") { | |
| v = stringify(v, 5, jsonReplacer, " "); | |
| } | |
| } | |
| return $el("div", { | |
| style: { | |
| color, | |
| }, | |
| textContent: v, | |
| }); | |
| }) | |
| ); | |
| }); | |
| const grid = $el( | |
| "div.comfy-logging-logs", | |
| { | |
| style: { | |
| gridTemplateColumns: `repeat(${headers.length}, 1fr)`, | |
| }, | |
| }, | |
| [...headers, ...rows] | |
| ); | |
| const els = [grid]; | |
| if (!this.logging.enabled) { | |
| els.unshift( | |
| $el("h3", { | |
| style: { textAlign: "center" }, | |
| textContent: "Logging is disabled", | |
| }) | |
| ); | |
| } | |
| super.show($el("div", els)); | |
| } | |
| } | |
| export class ComfyLogging { | |
| /** | |
| * @type Array<{ source: string, type: string, timestamp: Date, message: any }> | |
| */ | |
| entries = []; | |
| #enabled; | |
| #console = {}; | |
| get enabled() { | |
| return this.#enabled; | |
| } | |
| set enabled(value) { | |
| if (value === this.#enabled) return; | |
| if (value) { | |
| this.patchConsole(); | |
| } else { | |
| this.unpatchConsole(); | |
| } | |
| this.#enabled = value; | |
| } | |
| constructor(app) { | |
| this.app = app; | |
| this.dialog = new ComfyLoggingDialog(this); | |
| this.addSetting(); | |
| this.catchUnhandled(); | |
| this.addInitData(); | |
| } | |
| addSetting() { | |
| const settingId = "Comfy.Logging.Enabled"; | |
| const htmlSettingId = settingId.replaceAll(".", "-"); | |
| const setting = this.app.ui.settings.addSetting({ | |
| id: settingId, | |
| name: settingId, | |
| defaultValue: true, | |
| type: (name, setter, value) => { | |
| return $el("tr", [ | |
| $el("td", [ | |
| $el("label", { | |
| textContent: "Logging", | |
| for: htmlSettingId, | |
| }), | |
| ]), | |
| $el("td", [ | |
| $el("input", { | |
| id: htmlSettingId, | |
| type: "checkbox", | |
| checked: value, | |
| onchange: (event) => { | |
| setter((this.enabled = event.target.checked)); | |
| }, | |
| }), | |
| $el("button", { | |
| textContent: "View Logs", | |
| onclick: () => { | |
| this.app.ui.settings.element.close(); | |
| this.dialog.show(); | |
| }, | |
| style: { | |
| fontSize: "14px", | |
| display: "block", | |
| marginTop: "5px", | |
| }, | |
| }), | |
| ]), | |
| ]); | |
| }, | |
| }); | |
| this.enabled = setting.value; | |
| } | |
| patchConsole() { | |
| // Capture common console outputs | |
| const self = this; | |
| for (const type of ["log", "warn", "error", "debug"]) { | |
| const orig = console[type]; | |
| this.#console[type] = orig; | |
| console[type] = function () { | |
| orig.apply(console, arguments); | |
| self.addEntry("console", type, ...arguments); | |
| }; | |
| } | |
| } | |
| unpatchConsole() { | |
| // Restore original console functions | |
| for (const type of Object.keys(this.#console)) { | |
| console[type] = this.#console[type]; | |
| } | |
| this.#console = {}; | |
| } | |
| catchUnhandled() { | |
| // Capture uncaught errors | |
| window.addEventListener("error", (e) => { | |
| this.addEntry("window", "error", e.error ?? "Unknown error"); | |
| return false; | |
| }); | |
| window.addEventListener("unhandledrejection", (e) => { | |
| this.addEntry("unhandledrejection", "error", e.reason ?? "Unknown error"); | |
| }); | |
| } | |
| clear() { | |
| this.entries = []; | |
| } | |
| addEntry(source, type, ...args) { | |
| if (this.enabled) { | |
| this.entries.push({ | |
| source, | |
| type, | |
| timestamp: new Date(), | |
| message: args, | |
| }); | |
| } | |
| } | |
| log(source, ...args) { | |
| this.addEntry(source, "log", ...args); | |
| } | |
| async addInitData() { | |
| if (!this.enabled) return; | |
| const source = "ComfyUI.Logging"; | |
| this.addEntry(source, "debug", { UserAgent: navigator.userAgent }); | |
| const systemStats = await api.getSystemStats(); | |
| this.addEntry(source, "debug", systemStats); | |
| } | |
| } | |