refactor(logging): centralize console output

This commit is contained in:
2026-06-18 13:34:19 +08:00
parent f600e11d55
commit 812a3e41b9
24 changed files with 261 additions and 166 deletions
+13 -37
View File
@@ -6,11 +6,9 @@
*/
import type { FetchHook } from "ofetch";
const TAG = "[API]";
import { AppEnvUtil, Logger } from "@/utils";
function isDev(): boolean {
return process.env.NODE_ENV !== "production";
}
const log = new Logger("ApiLoggingInterceptor");
function getRequestUrl(request: Request | string): string {
return typeof request === "string" ? request : request.url;
@@ -23,54 +21,32 @@ function getRequestMethod(
return request instanceof Request ? request.method : options.method ?? "GET";
}
function formatBody(body: unknown): string {
if (body === undefined || body === null) return "";
try {
if (body instanceof FormData) {
const entries: string[] = [];
body.forEach((value, key) => {
if (value instanceof File) {
entries.push(`${key}=File(${value.name})`);
} else {
entries.push(`${key}=${String(value)}`);
}
});
return `FormData{${entries.join(", ")}}`;
}
return JSON.stringify(body);
} catch {
return String(body);
}
}
export const onRequestLogging: FetchHook = async (ctx) => {
if (!isDev()) return;
if (AppEnvUtil.isProduction()) return;
const method = getRequestMethod(ctx.request, ctx.options);
const url = getRequestUrl(ctx.request);
console.log(
`${TAG} ${method} ${url}`,
ctx.options.body !== undefined ? formatBody(ctx.options.body) : ""
);
const body = Logger.formatValue(ctx.options.body);
log.debug(`${method} ${url}${body ? `\nrequest body:\n${body}` : ""}`);
};
export const onResponseLogging: FetchHook = async (ctx) => {
if (!isDev() || !ctx.response) return;
if (AppEnvUtil.isProduction() || !ctx.response) return;
const method = getRequestMethod(ctx.request, ctx.options);
const url = getRequestUrl(ctx.request);
console.log(
`${TAG}${ctx.response.status} ${method} ${url}`,
formatBody(ctx.response._data)
const body = Logger.formatValue(ctx.response._data);
log.debug(
`${ctx.response.status} ${method} ${url}${body ? `\nresponse body:\n${body}` : ""}`,
);
};
export const onErrorLogging: FetchHook = async (ctx) => {
if (!isDev()) return;
if (AppEnvUtil.isProduction()) return;
const status = ctx.response?.status ?? "N/A";
const method = getRequestMethod(ctx.request, ctx.options);
const url = getRequestUrl(ctx.request);
console.error(
`${TAG}${status} ${method} ${url}`,
ctx.error?.message ?? "Unknown error"
const body = Logger.formatValue(ctx.response?._data);
log.error(
`${status} ${method} ${url}\nerror: ${ctx.error?.message ?? "Unknown error"}${body ? `\nresponse body:\n${body}` : ""}`,
);
};
+15 -12
View File
@@ -6,9 +6,12 @@
* 原始 Dart: lib/core/net/interceptor/token_interceptor.dart + token_paths.dart
*/
import type { FetchHook } from "ofetch";
import { Logger } from "@/utils";
import { ApiPath } from "../../../data/services/api/api_path";
import { AuthStorage } from "../../../data/storage/auth/auth_storage";
const log = new Logger("TokenInterceptor");
/**
* 不需要 token 的路径
*/
@@ -47,41 +50,41 @@ async function getAuthToken(): Promise<string | null> {
const storage = AuthStorage.getInstance();
const loginR = await storage.getLoginToken();
if (loginR.success && loginR.data && loginR.data.length > 0) {
console.log("[token-interceptor] getAuthToken: using LOGIN token", {
log.debug({
length: loginR.data.length,
prefix: loginR.data.slice(0, 8),
});
}, "getAuthToken: using LOGIN token");
return loginR.data;
}
console.log("[token-interceptor] getAuthToken: no login token", {
log.debug({
success: loginR.success,
hasData: loginR.success ? !!loginR.data : false,
});
}, "getAuthToken: no login token");
const guestR = await storage.getGuestToken();
if (guestR.success && guestR.data && guestR.data.length > 0) {
console.log(
"[token-interceptor] getAuthToken: using GUEST token (login priority, no login available)",
log.debug(
{
length: guestR.data.length,
prefix: guestR.data.slice(0, 8),
},
"getAuthToken: using GUEST token (login priority, no login available)",
);
return guestR.data;
}
console.warn("[token-interceptor] getAuthToken: NO token available (will skip Authorization)", {
log.warn({
loginAttempted: true,
guestAttempted: true,
loginSuccess: loginR.success,
guestSuccess: guestR.success,
});
}, "getAuthToken: NO token available (will skip Authorization)");
return null;
}
export const onRequestToken: FetchHook = async (ctx) => {
const path = getRequestPath(ctx.request);
if (!needsToken(path)) {
console.log("[token-interceptor] onRequestToken SKIP (no-token path)", { path });
log.debug({ path }, "onRequestToken SKIP (no-token path)");
return;
}
@@ -91,13 +94,13 @@ export const onRequestToken: FetchHook = async (ctx) => {
const headers = new Headers(ctx.options.headers);
headers.set("Authorization", `Bearer ${token}`);
ctx.options.headers = headers;
console.log("[token-interceptor] onRequestToken SET Authorization header (via ctx.options.headers)", {
log.debug({
path,
length: token.length,
prefix: token.slice(0, 8),
});
}, "onRequestToken SET Authorization header (via ctx.options.headers)");
} else {
console.warn("[token-interceptor] onRequestToken NO token to set", { path });
log.warn({ path }, "onRequestToken NO token to set");
}
};
+5 -1
View File
@@ -8,8 +8,12 @@
* - 消费者可声明每条消息是否成功消费;不成功则重新入队
* - 队列可在任意时刻清空(dispose)
*/
import { Logger } from "@/utils";
export type MessageConsumer = (content: string) => boolean | Promise<boolean>;
const log = new Logger("MessageQueue");
export class MessageQueue {
private queue: string[] = [];
private timer: ReturnType<typeof setTimeout> | null = null;
@@ -38,7 +42,7 @@ export class MessageQueue {
try {
ok = await this.consumer(next);
} catch (e) {
console.error("[MessageQueue] consumer error", e);
log.error({ err: e }, "consumer error");
ok = false;
}
if (!ok) {