feat: add Logger

This commit is contained in:
WJG 2024-02-26 15:45:36 +08:00
parent 7ad50322fe
commit 2dc65da0fe
No known key found for this signature in database
GPG Key ID: 258474EF8590014A
17 changed files with 199 additions and 71 deletions

View File

@ -2,3 +2,6 @@
- ✅ Stream response
- ✅ Deactivate Xiaoai
- ✅ Update long/short memories
- ✅ Logger
- Docker
- Npm export

View File

@ -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 };

View File

@ -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<void>) {
main()
.then(async () => {
await kPrisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
kDBLogger.error(e);
await kPrisma.$disconnect();
process.exit(1);
});

View File

@ -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;
});
}

View File

@ -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;
});
}

View File

@ -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;
});
}

View File

@ -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;
});
}

View File

@ -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;
});
}

View File

@ -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;
});
}

View File

@ -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<any> & {
cookies?: Record<string, string | number | boolean | undefined>;
};
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

View File

@ -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);
}
}

View File

@ -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();

View File

@ -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);
}

110
src/utils/log.ts Normal file
View File

@ -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();

View File

@ -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}`;
}

View File

@ -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);

9
tests/log.ts Normal file
View File

@ -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!" });
}