From 2dc65da0fe78a11f1c46e259bae3932c5d9f43f4 Mon Sep 17 00:00:00 2001 From: WJG Date: Mon, 26 Feb 2024 15:45:36 +0800 Subject: [PATCH] feat: add Logger --- TODO.md | 3 + src/services/bot/config.ts | 14 ++-- src/services/db/index.ts | 4 +- src/services/db/memory-long-term.ts | 10 +-- src/services/db/memory-short-term.ts | 14 ++-- src/services/db/memory.ts | 10 +-- src/services/db/message.ts | 10 +-- src/services/db/room.ts | 10 +-- src/services/db/user.ts | 10 +-- src/services/http.ts | 6 +- src/services/openai.ts | 30 +++----- src/services/speaker/base.ts | 11 +-- src/services/speaker/speaker.ts | 4 +- src/utils/log.ts | 110 +++++++++++++++++++++++++++ src/utils/string.ts | 11 +++ tests/index.ts | 4 +- tests/log.ts | 9 +++ 17 files changed, 199 insertions(+), 71 deletions(-) create mode 100644 src/utils/log.ts create mode 100644 tests/log.ts diff --git a/TODO.md b/TODO.md index 73f7278..2aebd80 100644 --- a/TODO.md +++ b/TODO.md @@ -2,3 +2,6 @@ - ✅ Stream response - ✅ Deactivate Xiaoai - ✅ Update long/short memories +- ✅ Logger +- Docker +- Npm export \ No newline at end of file diff --git a/src/services/bot/config.ts b/src/services/bot/config.ts index 8174bc3..716726f 100644 --- a/src/services/bot/config.ts +++ b/src/services/bot/config.ts @@ -4,6 +4,7 @@ import { readJSON, writeJSON } from "../../utils/io"; import { DeepPartial } from "../../utils/type"; import { RoomCRUD, getRoomID } from "../db/room"; import { UserCRUD } from "../db/user"; +import { Logger } from "../../utils/log"; const kDefaultMaster = { name: "用户", @@ -27,6 +28,7 @@ export interface IBotConfig { } class _BotConfig { + private _logger = Logger.create({ tag: "BotConfig" }); private botIndex?: IBotIndex; private _index_path = ".bot.json"; @@ -44,12 +46,12 @@ class _BotConfig { // create db records const bot = await UserCRUD.addOrUpdate(kDefaultBot); if (!bot) { - console.error("❌ create bot failed"); + this._logger.error("create bot failed"); return undefined; } const master = await UserCRUD.addOrUpdate(kDefaultMaster); if (!master) { - console.error("❌ create master failed"); + this._logger.error("create master failed"); return undefined; } const defaultRoomName = `${master.name}和${bot.name}的私聊`; @@ -59,7 +61,7 @@ class _BotConfig { description: defaultRoomName, }); if (!room) { - console.error("❌ create room failed"); + this._logger.error("create room failed"); return undefined; } this.botIndex = { @@ -70,17 +72,17 @@ class _BotConfig { } const bot = await UserCRUD.get(this.botIndex!.botId); if (!bot) { - console.error("❌ find bot failed"); + this._logger.error("find bot failed"); return undefined; } const master = await UserCRUD.get(this.botIndex!.masterId); if (!master) { - console.error("❌ find master failed"); + this._logger.error("find master failed"); return undefined; } const room = await RoomCRUD.get(getRoomID([bot, master])); if (!room) { - console.error("❌ find room failed"); + this._logger.error("find room failed"); return undefined; } return { bot, master, room }; diff --git a/src/services/db/index.ts b/src/services/db/index.ts index 248d4df..93800c9 100644 --- a/src/services/db/index.ts +++ b/src/services/db/index.ts @@ -1,16 +1,18 @@ import { PrismaClient } from "@prisma/client"; +import { Logger } from "../../utils/log"; export const k404 = -404; export const kPrisma = new PrismaClient(); +export const kDBLogger = Logger.create({ tag: "DB" }); export function runWithDB(main: () => Promise) { main() .then(async () => { await kPrisma.$disconnect(); }) .catch(async (e) => { - console.error(e); + kDBLogger.error(e); await kPrisma.$disconnect(); process.exit(1); }); diff --git a/src/services/db/memory-long-term.ts b/src/services/db/memory-long-term.ts index 83779ca..f852ae6 100644 --- a/src/services/db/memory-long-term.ts +++ b/src/services/db/memory-long-term.ts @@ -1,6 +1,6 @@ import { LongTermMemory, Room, User } from "@prisma/client"; import { removeEmpty } from "../../utils/base"; -import { getSkipWithCursor, k404, kPrisma } from "./index"; +import { getSkipWithCursor, k404, kDBLogger, kPrisma } from "./index"; class _LongTermMemoryCRUD { async count(options?: { cursorId?: number; room?: Room; owner?: User }) { @@ -14,14 +14,14 @@ class _LongTermMemoryCRUD { }, }) .catch((e) => { - console.error("❌ get longTermMemory count failed", e); + kDBLogger.error("get longTermMemory count failed", e); return -1; }); } async get(id: number) { return kPrisma.longTermMemory.findFirst({ where: { id } }).catch((e) => { - console.error("❌ get long term memory failed", id, e); + kDBLogger.error("get long term memory failed", id, e); return undefined; }); } @@ -53,7 +53,7 @@ class _LongTermMemoryCRUD { ...getSkipWithCursor(skip, cursorId), }) .catch((e) => { - console.error("❌ get long term memories failed", options, e); + kDBLogger.error("get long term memories failed", options, e); return []; }); return order === "desc" ? memories.reverse() : memories; @@ -82,7 +82,7 @@ class _LongTermMemoryCRUD { update: data, }) .catch((e) => { - console.error("❌ add longTermMemory to db failed", longTermMemory, e); + kDBLogger.error("add longTermMemory to db failed", longTermMemory, e); return undefined; }); } diff --git a/src/services/db/memory-short-term.ts b/src/services/db/memory-short-term.ts index 57376ce..03835fa 100644 --- a/src/services/db/memory-short-term.ts +++ b/src/services/db/memory-short-term.ts @@ -1,6 +1,6 @@ import { Room, ShortTermMemory, User } from "@prisma/client"; import { removeEmpty } from "../../utils/base"; -import { getSkipWithCursor, k404, kPrisma } from "./index"; +import { getSkipWithCursor, k404, kDBLogger, kPrisma } from "./index"; class _ShortTermMemoryCRUD { async count(options?: { cursorId?: number; room?: Room; owner?: User }) { @@ -14,14 +14,14 @@ class _ShortTermMemoryCRUD { }, }) .catch((e) => { - console.error("❌ get shortTermMemory count failed", e); + kDBLogger.error("get shortTermMemory count failed", e); return -1; }); } async get(id: number) { return kPrisma.shortTermMemory.findFirst({ where: { id } }).catch((e) => { - console.error("❌ get short term memory failed", id, e); + kDBLogger.error("get short term memory failed", id, e); return undefined; }); } @@ -53,7 +53,7 @@ class _ShortTermMemoryCRUD { ...getSkipWithCursor(skip, cursorId), }) .catch((e) => { - console.error("❌ get short term memories failed", options, e); + kDBLogger.error("get short term memories failed", options, e); return []; }); return order === "desc" ? memories.reverse() : memories; @@ -82,11 +82,7 @@ class _ShortTermMemoryCRUD { update: data, }) .catch((e) => { - console.error( - "❌ add shortTermMemory to db failed", - shortTermMemory, - e - ); + kDBLogger.error("add shortTermMemory to db failed", shortTermMemory, e); return undefined; }); } diff --git a/src/services/db/memory.ts b/src/services/db/memory.ts index 76f18c3..c88541b 100644 --- a/src/services/db/memory.ts +++ b/src/services/db/memory.ts @@ -1,6 +1,6 @@ import { Memory, Prisma, Room, User } from "@prisma/client"; -import { getSkipWithCursor, k404, kPrisma } from "./index"; import { removeEmpty } from "../../utils/base"; +import { getSkipWithCursor, k404, kDBLogger, kPrisma } from "./index"; class _MemoryCRUD { async count(options?: { cursorId?: number; room?: Room; owner?: User }) { @@ -14,7 +14,7 @@ class _MemoryCRUD { }, }) .catch((e) => { - console.error("❌ get memory count failed", e); + kDBLogger.error("get memory count failed", e); return -1; }); } @@ -33,7 +33,7 @@ class _MemoryCRUD { }, } = options ?? {}; return kPrisma.memory.findFirst({ where: { id }, include }).catch((e) => { - console.error("❌ get memory failed", id, e); + kDBLogger.error("get memory failed", id, e); return undefined; }); } @@ -72,7 +72,7 @@ class _MemoryCRUD { ...getSkipWithCursor(skip, cursorId), }) .catch((e) => { - console.error("❌ get memories failed", options, e); + kDBLogger.error("get memories failed", options, e); return []; }); return order === "desc" ? memories.reverse() : memories; @@ -98,7 +98,7 @@ class _MemoryCRUD { update: data, }) .catch((e) => { - console.error("❌ add memory to db failed", memory, e); + kDBLogger.error("add memory to db failed", memory, e); return undefined; }); } diff --git a/src/services/db/message.ts b/src/services/db/message.ts index 23545bc..633fe8a 100644 --- a/src/services/db/message.ts +++ b/src/services/db/message.ts @@ -1,6 +1,6 @@ import { Message, Prisma, Room, User } from "@prisma/client"; import { removeEmpty } from "../../utils/base"; -import { getSkipWithCursor, k404, kPrisma } from "./index"; +import { getSkipWithCursor, k404, kDBLogger, kPrisma } from "./index"; class _MessageCRUD { async count(options?: { cursorId?: number; room?: Room; sender?: User }) { @@ -14,7 +14,7 @@ class _MessageCRUD { }, }) .catch((e) => { - console.error("❌ get message count failed", e); + kDBLogger.error("get message count failed", e); return -1; }); } @@ -27,7 +27,7 @@ class _MessageCRUD { ) { const { include = { sender: true } } = options ?? {}; return kPrisma.message.findFirst({ where: { id }, include }).catch((e) => { - console.error("❌ get message failed", id, e); + kDBLogger.error("get message failed", id, e); return undefined; }); } @@ -62,7 +62,7 @@ class _MessageCRUD { ...getSkipWithCursor(skip, cursorId), }) .catch((e) => { - console.error("❌ get messages failed", options, e); + kDBLogger.error("get messages failed", options, e); return []; }); return order === "desc" ? messages.reverse() : messages; @@ -89,7 +89,7 @@ class _MessageCRUD { update: data, }) .catch((e) => { - console.error("❌ add message to db failed", message, e); + kDBLogger.error("add message to db failed", message, e); return undefined; }); } diff --git a/src/services/db/room.ts b/src/services/db/room.ts index f3d6787..4166450 100644 --- a/src/services/db/room.ts +++ b/src/services/db/room.ts @@ -1,5 +1,5 @@ import { Prisma, Room, User } from "@prisma/client"; -import { k404, kPrisma, getSkipWithCursor } from "./index"; +import { k404, kPrisma, getSkipWithCursor, kDBLogger } from "./index"; export function getRoomID(users: User[]) { return users @@ -22,7 +22,7 @@ class _RoomCRUD { }, }) .catch((e) => { - console.error("❌ get room count failed", e); + kDBLogger.error("get room count failed", e); return -1; }); } @@ -35,7 +35,7 @@ class _RoomCRUD { ) { const { include = { members: true } } = options ?? {}; return kPrisma.room.findFirst({ where: { id } }).catch((e) => { - console.error("❌ get room failed", id, e); + kDBLogger.error("get room failed", id, e); return undefined; }); } @@ -68,7 +68,7 @@ class _RoomCRUD { ...getSkipWithCursor(skip, cursorId), }) .catch((e) => { - console.error("❌ get rooms failed", options, e); + kDBLogger.error("get rooms failed", options, e); return []; }); return order === "desc" ? rooms.reverse() : rooms; @@ -89,7 +89,7 @@ class _RoomCRUD { update: room, }) .catch((e) => { - console.error("❌ add room to db failed", room, e); + kDBLogger.error("add room to db failed", room, e); return undefined; }); } diff --git a/src/services/db/user.ts b/src/services/db/user.ts index 8f3c401..0b0d4fd 100644 --- a/src/services/db/user.ts +++ b/src/services/db/user.ts @@ -1,10 +1,10 @@ import { Prisma, User } from "@prisma/client"; -import { getSkipWithCursor, k404, kPrisma } from "./index"; +import { getSkipWithCursor, k404, kDBLogger, kPrisma } from "./index"; class _UserCRUD { async count() { return kPrisma.user.count().catch((e) => { - console.error("❌ get user count failed", e); + kDBLogger.error("get user count failed", e); return -1; }); } @@ -17,7 +17,7 @@ class _UserCRUD { ) { const { include = { rooms: false } } = options ?? {}; return kPrisma.user.findFirst({ where: { id }, include }).catch((e) => { - console.error("❌ get user failed", id, e); + kDBLogger.error("get user failed", id, e); return undefined; }); } @@ -47,7 +47,7 @@ class _UserCRUD { ...getSkipWithCursor(skip, cursorId), }) .catch((e) => { - console.error("❌ get users failed", options, e); + kDBLogger.error("get users failed", options, e); return []; }); return order === "desc" ? users.reverse() : users; @@ -68,7 +68,7 @@ class _UserCRUD { update: user, }) .catch((e) => { - console.error("❌ add user to db failed", user, e); + kDBLogger.error("add user to db failed", user, e); return undefined; }); } diff --git a/src/services/http.ts b/src/services/http.ts index a265c25..2bf3080 100644 --- a/src/services/http.ts +++ b/src/services/http.ts @@ -1,6 +1,7 @@ import axios, { AxiosRequestConfig, CreateAxiosDefaults } from "axios"; import { HttpsProxyAgent } from "https-proxy-agent"; import { isNotEmpty } from "../utils/is"; +import { Logger } from "../utils/log"; export const kProxyAgent = new HttpsProxyAgent( process.env.HTTP_PROXY ?? "http://127.0.0.1:7890" @@ -29,6 +30,7 @@ type RequestConfig = AxiosRequestConfig & { cookies?: Record; }; +const _logger = Logger.create({ tag: "Http" }); _http.interceptors.response.use( (res) => { const config: any = res.config; @@ -45,8 +47,8 @@ _http.interceptors.response.use( code: error.code ?? "UNKNOWN CODE", message: error.message ?? "UNKNOWN ERROR", }; - console.error( - "❌ Network request failed:", + _logger.error( + "Network request failed:", apiError.code, apiError.message, error diff --git a/src/services/openai.ts b/src/services/openai.ts index e51be69..90defab 100644 --- a/src/services/openai.ts +++ b/src/services/openai.ts @@ -8,6 +8,7 @@ import { kEnvs } from "../utils/env"; import { kProxyAgent } from "./http"; import { withDefault } from "../utils/base"; import { ChatCompletionCreateParamsBase } from "openai/resources/chat/completions"; +import { Logger } from "../utils/log"; export interface ChatOptions { user: string; @@ -19,6 +20,7 @@ export interface ChatOptions { } class OpenAIClient { + private _logger = Logger.create({ tag: "OpenAI" }); private _client = new OpenAI({ httpAgent: kProxyAgent, apiKey: kEnvs.OPENAI_API_KEY!, @@ -44,9 +46,8 @@ class OpenAIClient { requestId, model = kEnvs.OPENAI_MODEL ?? "gpt-3.5-turbo-0125", } = options; - console.log( - ` -🔥🔥🔥 onAskAI start + this._logger.log( + `🔥 onAskAI 🤖️ System: ${system ?? "None"} 😊 User: ${user} `.trim() @@ -71,16 +72,11 @@ class OpenAIClient { { signal } ) .catch((e) => { - console.error("❌ openai chat failed", e); + this._logger.error("openai chat failed", e); return null; }); const message = chatCompletion?.choices?.[0]?.message; - console.log( - ` - ✅✅✅ onAskAI end - 🤖️ Answer: ${message?.content ?? "None"} - `.trim() - ); + this._logger.success(`🤖️ Answer: ${message?.content ?? "None"}`.trim()); return message; } @@ -98,9 +94,8 @@ class OpenAIClient { onStream, model = kEnvs.OPENAI_MODEL ?? "gpt-3.5-turbo-0125", } = options; - console.log( - ` -🔥🔥🔥 onAskAI start + this._logger.log( + `🔥 onAskAI 🤖️ System: ${system ?? "None"} 😊 User: ${user} `.trim() @@ -117,7 +112,7 @@ class OpenAIClient { response_format: jsonMode ? { type: "json_object" } : undefined, }) .catch((e) => { - console.error("❌ openai chat failed", e); + this._logger.error("❌ openai chat failed", e); return null; }); if (!stream) { @@ -140,12 +135,7 @@ class OpenAIClient { content += text; } } - console.log( - ` - ✅✅✅ onAskAI end - 🤖️ Answer: ${content ?? "None"} - `.trim() - ); + this._logger.success(`🤖️ Answer: ${content ?? "None"}`.trim()); return withDefault(content, undefined); } } diff --git a/src/services/speaker/base.ts b/src/services/speaker/base.ts index 6edbcde..e6d94a7 100644 --- a/src/services/speaker/base.ts +++ b/src/services/speaker/base.ts @@ -1,12 +1,12 @@ -import { assert } from "console"; import { + MiIOT, + MiNA, MiServiceConfig, getMiIOT, getMiNA, - MiNA, - MiIOT, } from "mi-service-lite"; import { sleep } from "../../utils/base"; +import { Logger } from "../../utils/log"; import { Http } from "../http"; import { StreamResponse } from "./stream"; @@ -26,6 +26,7 @@ export type BaseSpeakerConfig = MiServiceConfig & { }; export class BaseSpeaker { + logger = Logger.create({ tag: "Speaker" }); MiNA?: MiNA; MiIOT?: MiIOT; @@ -42,7 +43,7 @@ export class BaseSpeaker { async initMiServices() { this.MiNA = await getMiNA(this.config); this.MiIOT = await getMiIOT(this.config); - assert(!!this.MiNA && !!this.MiIOT, "❌ init Mi Services failed"); + this.logger.assert(!!this.MiNA && !!this.MiIOT, "init Mi Services failed"); } wakeUp() { @@ -169,7 +170,7 @@ export class BaseSpeaker { await this.unWakeUp(); } await this.MiNA!.play(args); - console.log("✅ " + ttsText ?? audio); + this.logger.success(ttsText ?? audio); // 等待回答播放完毕 while (true) { const res = await this.MiNA!.getStatus(); diff --git a/src/services/speaker/speaker.ts b/src/services/speaker/speaker.ts index 8ea8928..039f7a7 100644 --- a/src/services/speaker/speaker.ts +++ b/src/services/speaker/speaker.ts @@ -64,13 +64,13 @@ export class Speaker extends BaseSpeaker { if (!this.MiNA) { this.stop(); } - console.log("✅ 服务已启动..."); + this.logger.success("服务已启动..."); this.activeKeepAliveMode(); while (this.status === "running") { const nextMsg = await this.fetchNextMessage(); if (nextMsg) { this.responding = false; - console.log("🔥 " + nextMsg.text); + this.logger.log("🔥 " + nextMsg.text); // 异步处理消息,不阻塞正常消息拉取 this.onMessage(nextMsg); } diff --git a/src/utils/log.ts b/src/utils/log.ts new file mode 100644 index 0000000..4a0e981 --- /dev/null +++ b/src/utils/log.ts @@ -0,0 +1,110 @@ +import { toSet } from "./base"; +import { isString } from "./is"; +import { formatDateTime } from "./string"; + +class _LoggerManager { + disable = false; + _excludes: string[] = []; + + excludes(tags: string[]) { + this._excludes = toSet(this._excludes.concat(tags)); + } + + includes(tags: string[]) { + for (const tag of tags) { + const idx = this._excludes.indexOf(tag); + if (idx > -1) { + this._excludes.splice(idx, 1); + } + } + } + + private _getLogs(tag: string, ...args: any[]) { + if (this.disable || this._excludes.includes(tag)) { + return []; + } + const date = formatDateTime(new Date()); + let prefix = `${date} ${tag} `; + if (args.length < 1) { + args = [undefined]; + } + if (isString(args[0])) { + prefix += args[0]; + args = args.slice(1); + } + return [prefix, ...args]; + } + + log(tag: string, args: any[] = []) { + const logs = this._getLogs(tag, ...args); + if (logs.length > 0) { + console.log(...logs); + } + } + + success(tag: string, args: any[]) { + const logs = this._getLogs(tag + " ✅", ...args); + if (logs.length > 0) { + console.log(...logs); + } + } + + error(tag: string, args: any[]) { + const logs = this._getLogs(tag + " ❌", ...args); + if (logs.length > 0) { + console.error(...logs); + } + } + + assert(tag: string, value: any, args: any[]) { + const logs = this._getLogs(tag + " ❌", ...args); + if (!value) { + console.error(...logs); + throw Error("❌ Assertion failed"); + } + } +} + +export const LoggerManager = new _LoggerManager(); + +export interface LoggerConfig { + tag?: string; + disable?: boolean; +} +class _Logger { + tag: string; + disable: boolean; + constructor(config?: LoggerConfig) { + const { tag = "default", disable = false } = config ?? {}; + this.tag = tag; + this.disable = disable; + } + + create(config?: LoggerConfig) { + return new _Logger(config); + } + + log(...args: any[]) { + if (!this.disable) { + LoggerManager.log(this.tag, args); + } + } + + success(...args: any[]) { + if (!this.disable) { + LoggerManager.success(this.tag, args); + } + } + + error(...args: any[]) { + if (!this.disable) { + LoggerManager.error(this.tag, args); + } + } + + assert(value: any, ...args: any[]) { + LoggerManager.assert(this.tag, value, args); + } +} + +export const Logger = new _Logger(); diff --git a/src/utils/string.ts b/src/utils/string.ts index 31748a4..c83e17d 100644 --- a/src/utils/string.ts +++ b/src/utils/string.ts @@ -52,3 +52,14 @@ export function formatMsg(msg: { const { name, text, timestamp } = msg; return `${toUTC8Time(new Date(timestamp))} ${name}: ${text}`; } + +export function formatDateTime(date: Date) { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + const hours = String(date.getHours()).padStart(2, "0"); + const minutes = String(date.getMinutes()).padStart(2, "0"); + const seconds = String(date.getSeconds()).padStart(2, "0"); + + return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`; +} diff --git a/tests/index.ts b/tests/index.ts index 3063642..8cf6e2f 100644 --- a/tests/index.ts +++ b/tests/index.ts @@ -6,6 +6,7 @@ import { testDB } from "./db"; import { testSpeaker } from "./speaker"; import { testOpenAI } from "./openai"; import { testMyBot } from "./bot"; +import { testLog } from "./log"; dotenv.config(); @@ -14,7 +15,8 @@ async function main() { // testDB(); // testSpeaker(); // testOpenAI(); - testMyBot(); + // testMyBot(); + testLog(); } runWithDB(main); diff --git a/tests/log.ts b/tests/log.ts new file mode 100644 index 0000000..40a1c7e --- /dev/null +++ b/tests/log.ts @@ -0,0 +1,9 @@ +import { Logger } from "../src/utils/log"; + +export function testLog() { + Logger.log("你好", ["世界"], { hello: "world!" }); + Logger.success("你好", ["世界"], { hello: "world!" }); + Logger.error("你好", ["世界"], { hello: "world!" }); + Logger.assert(true, "你好 111", ["世界"], { hello: "world!" }); + Logger.assert(false, "你好 222", ["世界"], { hello: "world!" }); +}