feat: 全栈 Logger 依赖注入 — DB/Route/AI 层传参 + 前端 Logger + 测试更新 + 归档 add-frontend-logger

This commit is contained in:
2026-06-01 20:32:19 +08:00
parent 4c72754739
commit 844562303c
60 changed files with 1648 additions and 778 deletions

View File

@@ -1,12 +1,18 @@
import type Database from "bun:sqlite";
import type { CreateConversationRequest, RuntimeMode } from "../../../shared/api";
import type { Logger } from "../../logger";
import { createConversation } from "../../db/conversations";
import { createApiError, jsonResponse } from "../../helpers";
import { validateIdParam } from "../../middleware";
export async function handleCreateConversation(req: Request, db: Database, mode: RuntimeMode): Promise<Response> {
export async function handleCreateConversation(
req: Request,
db: Database,
mode: RuntimeMode,
logger: Logger,
): Promise<Response> {
const url = new URL(req.url);
const projectId = url.pathname.split("/")[3];
@@ -16,14 +22,16 @@ export async function handleCreateConversation(req: Request, db: Database, mode:
let body: CreateConversationRequest = {};
try {
body = (await req.json()) as CreateConversationRequest;
} catch {
} catch (e: unknown) {
logger.warn({ error: e instanceof Error ? e.message : String(e) }, "请求 JSON 解析失败");
// empty body is ok, defaults will be used
}
const result = createConversation(db, validated.id, body.modelId);
const result = createConversation(db, validated.id, logger, body.modelId);
if ("error" in result) {
return jsonResponse(createApiError(result.error, result.status), { mode, status: result.status });
}
logger.info({ conversationId: result.conversation.id, projectId: validated.id }, "会话创建成功");
return jsonResponse({ conversation: result.conversation }, { mode, status: 201 });
}

View File

@@ -1,12 +1,13 @@
import type Database from "bun:sqlite";
import type { RuntimeMode } from "../../../shared/api";
import type { Logger } from "../../logger";
import { deleteConversation, getConversation } from "../../db/conversations";
import { createApiError, jsonResponse } from "../../helpers";
import { validateIdParam } from "../../middleware";
export function handleDeleteConversation(req: Request, db: Database, mode: RuntimeMode): Response {
export function handleDeleteConversation(req: Request, db: Database, mode: RuntimeMode, logger: Logger): Response {
const parts = new URL(req.url).pathname.split("/");
const projectId = parts[3];
const conversationId = parts[5];
@@ -26,10 +27,11 @@ export function handleDeleteConversation(req: Request, db: Database, mode: Runti
return jsonResponse(createApiError("会话不属于该项目", 403), { mode, status: 403 });
}
const result = deleteConversation(db, validatedConv.id);
const result = deleteConversation(db, validatedConv.id, logger);
if ("error" in result) {
return jsonResponse(createApiError(result.error, result.status), { mode, status: result.status });
}
logger.info({ conversationId: validatedConv.id }, "会话删除成功");
return jsonResponse({ success: true }, { mode });
}

View File

@@ -1,12 +1,13 @@
import type Database from "bun:sqlite";
import type { RuntimeMode } from "../../../shared/api";
import type { Logger } from "../../logger";
import { getConversation } from "../../db/conversations";
import { createApiError, jsonResponse } from "../../helpers";
import { validateIdParam } from "../../middleware";
export function handleGetConversation(req: Request, db: Database, mode: RuntimeMode): Response {
export function handleGetConversation(req: Request, db: Database, mode: RuntimeMode, _logger: Logger): Response {
const parts = new URL(req.url).pathname.split("/");
const projectId = parts[3];
const conversationId = parts[5];

View File

@@ -1,12 +1,13 @@
import type Database from "bun:sqlite";
import type { RuntimeMode } from "../../../shared/api";
import type { Logger } from "../../logger";
import { listConversations } from "../../db/conversations";
import { jsonResponse } from "../../helpers";
import { validateIdParam, validatePagination } from "../../middleware";
export function handleListConversations(req: Request, db: Database, mode: RuntimeMode): Response {
export function handleListConversations(req: Request, db: Database, mode: RuntimeMode, _logger: Logger): Response {
const url = new URL(req.url);
const projectId = url.pathname.split("/")[3];

View File

@@ -1,12 +1,13 @@
import type Database from "bun:sqlite";
import type { RuntimeMode } from "../../../shared/api";
import type { Logger } from "../../logger";
import { getConversation, listMessages } from "../../db/conversations";
import { createApiError, jsonResponse } from "../../helpers";
import { validateIdParam, validatePagination } from "../../middleware";
export function handleListMessages(req: Request, db: Database, mode: RuntimeMode): Response {
export function handleListMessages(req: Request, db: Database, mode: RuntimeMode, _logger: Logger): Response {
const parts = new URL(req.url).pathname.split("/");
const projectId = parts[3];
const conversationId = parts[5];

View File

@@ -29,7 +29,8 @@ export async function handleSendChat(req: Request, db: Database, mode: RuntimeMo
let body: { conversationId?: string; messages?: UIMessage[] };
try {
body = (await req.json()) as typeof body;
} catch {
} catch (e) {
logger.warn({ error: e instanceof Error ? e.message : String(e) }, "请求 JSON 解析失败");
return jsonResponse(createApiError("Invalid JSON body", 400), { mode, status: 400 });
}
@@ -64,12 +65,16 @@ export async function handleSendChat(req: Request, db: Database, mode: RuntimeMo
?.filter((p) => p.type === "text")
.map((p) => p.text)
.join("") ?? "";
createMessage(db, {
content,
conversationId: conversation.id,
parts: JSON.stringify(lastMsg.parts ?? []),
role: "user",
});
createMessage(
db,
{
content,
conversationId: conversation.id,
parts: JSON.stringify(lastMsg.parts ?? []),
role: "user",
},
logger,
);
}
updateConversationTimestamp(db, conversation.id);
@@ -114,12 +119,16 @@ export async function handleSendChat(req: Request, db: Database, mode: RuntimeMo
.filter((p): p is { text: string; type: "text" } => p.type === "text")
.map((p) => p.text)
.join("");
createMessage(db, {
content: text,
conversationId: conversation.id,
parts: JSON.stringify(responseMessage.parts),
role: "assistant",
});
createMessage(
db,
{
content: text,
conversationId: conversation.id,
parts: JSON.stringify(responseMessage.parts),
role: "assistant",
},
logger,
);
updateConversationTimestamp(db, conversation.id);
},
uiMessages: body.messages,
@@ -138,7 +147,7 @@ function generateConversationTitle(
logger: Logger,
): void {
if (firstUserText.length <= 5) {
updateConversation(db, conversationId, { title: firstUserText });
updateConversation(db, conversationId, { title: firstUserText }, logger);
return;
}
@@ -149,13 +158,13 @@ function generateConversationTitle(
})
.then((result) => {
const title = result.text.trim().slice(0, 10);
updateConversation(db, conversationId, { title: title || firstUserText.slice(0, 10) });
updateConversation(db, conversationId, { title: title || firstUserText.slice(0, 10) }, logger);
})
.catch((titleError: unknown) => {
const titleMsg = titleError instanceof Error ? titleError.message : String(titleError);
logger.error({ conversationId, error: titleMsg }, "标题生成失败");
try {
updateConversation(db, conversationId, { title: firstUserText.slice(0, 10) });
updateConversation(db, conversationId, { title: firstUserText.slice(0, 10) }, logger);
} catch {
logger.error({ conversationId }, "标题兜底更新失败");
}

View File

@@ -1,12 +1,18 @@
import type Database from "bun:sqlite";
import type { RuntimeMode, UpdateConversationRequest } from "../../../shared/api";
import type { Logger } from "../../logger";
import { getConversation, updateConversation } from "../../db/conversations";
import { createApiError, jsonResponse } from "../../helpers";
import { validateIdParam } from "../../middleware";
export async function handleUpdateConversation(req: Request, db: Database, mode: RuntimeMode): Promise<Response> {
export async function handleUpdateConversation(
req: Request,
db: Database,
mode: RuntimeMode,
logger: Logger,
): Promise<Response> {
const url = new URL(req.url);
const parts = url.pathname.split("/");
const projectId = parts[3];
@@ -30,7 +36,8 @@ export async function handleUpdateConversation(req: Request, db: Database, mode:
let body: UpdateConversationRequest;
try {
body = (await req.json()) as UpdateConversationRequest;
} catch {
} catch (e: unknown) {
logger.warn({ error: e instanceof Error ? e.message : String(e) }, "请求 JSON 解析失败");
return jsonResponse(createApiError("Invalid JSON body", 400), { mode, status: 400 });
}
@@ -38,10 +45,11 @@ export async function handleUpdateConversation(req: Request, db: Database, mode:
return jsonResponse(createApiError("至少需要传 modelId 或 title", 400), { mode, status: 400 });
}
const result = updateConversation(db, validatedConv.id, body);
const result = updateConversation(db, validatedConv.id, body, logger);
if ("error" in result) {
return jsonResponse(createApiError(result.error, result.status), { mode, status: result.status });
}
logger.info({ conversationId: result.conversation.id }, "会话更新成功");
return jsonResponse({ conversation: result.conversation }, { mode });
}