Files
QZMusic-Web/src/stores/log.ts

149 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { defineStore } from 'pinia';
import { ref, watch } from 'vue';
export type LogLevel = 'info' | 'warn' | 'error' | 'debug';
export interface LogEntry {
id: number;
time: string;
level: LogLevel;
module: string;
message: string;
detail?: any;
}
const MAX_LOGS = 500;
const STORAGE_KEY = 'qz-app-logs';
const LEVEL_ORDER: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3 };
function loadFromStorage(): LogEntry[] {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (raw) {
const parsed = JSON.parse(raw);
if (Array.isArray(parsed)) return parsed as LogEntry[];
}
} catch {
// ignore
}
return [];
}
export const useLogStore = defineStore('log', () => {
const logs = ref<LogEntry[]>(loadFromStorage());
let nextId = logs.value.length > 0 ? Math.max(...logs.value.map((l) => l.id)) + 1 : 1;
watch(
logs,
(newLogs) => {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(newLogs));
} catch {
// ignore quota issues
}
},
{ deep: true }
);
const add = (level: LogLevel, module: string, message: string, detail?: any) => {
const now = new Date();
const hh = now.getHours().toString().padStart(2, '0');
const mm = now.getMinutes().toString().padStart(2, '0');
const ss = now.getSeconds().toString().padStart(2, '0');
const ms = now.getMilliseconds().toString().padStart(3, '0');
// Error 对象属性不可枚举JSON.stringify 会得到 {}),这里先规范化
let normalizedDetail = detail;
if (detail instanceof Error) {
normalizedDetail = {
name: detail.name,
message: detail.message,
stack: detail.stack,
cause: (detail as any).cause,
__isError: true,
};
} else if (detail && typeof detail === 'object') {
// 避免 Proxy 等响应式包装的干扰 — 简单浅拷贝为纯对象
try {
normalizedDetail = JSON.parse(JSON.stringify(detail, (_, v) => {
if (v instanceof Error) {
return { name: v.name, message: v.message, stack: v.stack, __isError: true };
}
return v;
}));
} catch {
normalizedDetail = String(detail);
}
}
const entry: LogEntry = {
id: nextId++,
time: `${hh}:${mm}:${ss}.${ms}`,
level,
module,
message,
detail: normalizedDetail,
};
logs.value.push(entry);
if (logs.value.length > MAX_LOGS) {
logs.value = logs.value.slice(-MAX_LOGS);
}
// 同步到浏览器控制台,方便调试
const consoleArgs = [`[${entry.time}] [${level.toUpperCase()}] [${module}]`, message];
if (detail !== undefined) consoleArgs.push(detail);
try {
if (level === 'error') console.error(...consoleArgs);
else if (level === 'warn') console.warn(...consoleArgs);
else if (level === 'debug') console.debug(...consoleArgs);
else console.log(...consoleArgs);
} catch {
// ignore
}
};
const info = (module: string, message: string, detail?: any) => add('info', module, message, detail);
const warn = (module: string, message: string, detail?: any) => add('warn', module, message, detail);
const error = (module: string, message: string, detail?: any) => add('error', module, message, detail);
const debug = (module: string, message: string, detail?: any) => add('debug', module, message, detail);
const clear = () => {
logs.value = [];
nextId = 1;
};
const filter = (module?: string, level?: LogLevel) => {
return logs.value.filter((l) => {
if (module && l.module !== module) return false;
if (level && LEVEL_ORDER[l.level] < LEVEL_ORDER[level]) return false;
return true;
});
};
const moduleList = () => {
const set = new Set<string>();
for (const l of logs.value) set.add(l.module);
return Array.from(set).sort();
};
const countByLevel = () => {
const c = { info: 0, warn: 0, error: 0, debug: 0 };
for (const l of logs.value) c[l.level]++;
return c;
};
return {
logs,
info,
warn,
error,
debug,
add,
clear,
filter,
moduleList,
countByLevel,
};
});