chore: govern rmbg service logging
This commit is contained in:
parent
36f3de8321
commit
8e91c00c74
@ -6,8 +6,11 @@ import {
|
|||||||
} from "@/lib/db/queries";
|
} from "@/lib/db/queries";
|
||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
import { generatedImage } from "@/lib/db/schema";
|
import { generatedImage } from "@/lib/db/schema";
|
||||||
|
import { createLogger } from "@/lib/observability/logger";
|
||||||
import { uploadFile } from "@/lib/storage/service";
|
import { uploadFile } from "@/lib/storage/service";
|
||||||
|
|
||||||
|
const logger = createLogger("rmbg-service");
|
||||||
|
|
||||||
export type RmbgProvider = "rmbg" | "photoroom" | "aoscdn";
|
export type RmbgProvider = "rmbg" | "photoroom" | "aoscdn";
|
||||||
|
|
||||||
type RmbgConfig = {
|
type RmbgConfig = {
|
||||||
@ -73,7 +76,7 @@ async function createThumbnail(buffer: Buffer, mimeType: string): Promise<Buffer
|
|||||||
.png({ quality: 80 })
|
.png({ quality: 80 })
|
||||||
.toBuffer();
|
.toBuffer();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[RMBG] Failed to create thumbnail:", error);
|
logger.warn("thumbnail_creation_failed", error);
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,12 +108,20 @@ async function uploadBufferToS3(
|
|||||||
uploadFile(originalFilename, originalArrayBuffer),
|
uploadFile(originalFilename, originalArrayBuffer),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
logger.info("storage_upload_completed", {
|
||||||
|
mimeType,
|
||||||
|
originalBytes: buffer.length,
|
||||||
|
originalUrl: originalResult.url,
|
||||||
|
thumbnailBytes: thumbnailBuffer.length,
|
||||||
|
thumbnailUrl: thumbnailResult.url,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
thumbnail: thumbnailResult.url,
|
thumbnail: thumbnailResult.url,
|
||||||
original: originalResult.url,
|
original: originalResult.url,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[RMBG] Failed to upload to S3:", error);
|
logger.error("storage_upload_failed", error);
|
||||||
throw new RmbgServiceError(
|
throw new RmbgServiceError(
|
||||||
"图片上传到存储桶失败",
|
"图片上传到存储桶失败",
|
||||||
500,
|
500,
|
||||||
@ -190,7 +201,7 @@ async function prepareImageForAos(
|
|||||||
filename: `image.${filenameExt}`,
|
filename: `image.${filenameExt}`,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[AOS RMBG] 图片预处理失败,继续使用原图:", error);
|
logger.warn("aos_image_preprocess_failed", error);
|
||||||
return { buffer: imageBuffer, mimeType: "image/png", filename: "image.png" };
|
return { buffer: imageBuffer, mimeType: "image/png", filename: "image.png" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -228,7 +239,10 @@ async function processWithRMBG(
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
console.error("[RMBG API] 请求失败:", { status: response.status, error: errorText });
|
logger.warn("rmbg_provider_request_failed", {
|
||||||
|
errorLength: errorText.length,
|
||||||
|
status: response.status,
|
||||||
|
});
|
||||||
return { success: false, error: errorText || response.statusText };
|
return { success: false, error: errorText || response.statusText };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,6 +261,7 @@ async function processWithRMBG(
|
|||||||
if (error instanceof Error && error.name === "AbortError") {
|
if (error instanceof Error && error.name === "AbortError") {
|
||||||
return { success: false, error: "请求超时(30秒)" };
|
return { success: false, error: "请求超时(30秒)" };
|
||||||
}
|
}
|
||||||
|
logger.warn("rmbg_provider_request_error", error);
|
||||||
return { success: false, error: error instanceof Error ? error.message : "未知错误" };
|
return { success: false, error: error instanceof Error ? error.message : "未知错误" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -282,7 +297,10 @@ async function processWithPhotoRoom(
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
console.error("[PhotoRoom API] 请求失败:", { status: response.status, error: errorText });
|
logger.warn("photoroom_provider_request_failed", {
|
||||||
|
errorLength: errorText.length,
|
||||||
|
status: response.status,
|
||||||
|
});
|
||||||
return { success: false, error: `PhotoRoom API错误: ${response.status} - ${errorText}` };
|
return { success: false, error: `PhotoRoom API错误: ${response.status} - ${errorText}` };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,7 +314,7 @@ async function processWithPhotoRoom(
|
|||||||
if (error instanceof Error && error.name === "AbortError") {
|
if (error instanceof Error && error.name === "AbortError") {
|
||||||
return { success: false, error: "PhotoRoom请求超时(60秒)" };
|
return { success: false, error: "PhotoRoom请求超时(60秒)" };
|
||||||
}
|
}
|
||||||
console.error("[PhotoRoom] Error:", error);
|
logger.warn("photoroom_provider_request_error", error);
|
||||||
return { success: false, error: error instanceof Error ? error.message : "未知错误" };
|
return { success: false, error: error instanceof Error ? error.message : "未知错误" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -319,7 +337,9 @@ async function processWithAosSegmentation(
|
|||||||
try {
|
try {
|
||||||
const normalizedImage = await prepareImageForAos(imageBuffer);
|
const normalizedImage = await prepareImageForAos(imageBuffer);
|
||||||
const createForm = new FormData();
|
const createForm = new FormData();
|
||||||
const imageBlob = new Blob([new Uint8Array(normalizedImage.buffer)], { type: normalizedImage.mimeType });
|
const imageBlob = new Blob([new Uint8Array(normalizedImage.buffer)], {
|
||||||
|
type: normalizedImage.mimeType,
|
||||||
|
});
|
||||||
createForm.append("image_file", imageBlob, normalizedImage.filename);
|
createForm.append("image_file", imageBlob, normalizedImage.filename);
|
||||||
createForm.append("sync", "0");
|
createForm.append("sync", "0");
|
||||||
createForm.append("return_type", "1");
|
createForm.append("return_type", "1");
|
||||||
@ -343,6 +363,10 @@ async function processWithAosSegmentation(
|
|||||||
|
|
||||||
const createPayload = await createResponse.json().catch(() => null);
|
const createPayload = await createResponse.json().catch(() => null);
|
||||||
if (!createResponse.ok || !createPayload?.data?.task_id) {
|
if (!createResponse.ok || !createPayload?.data?.task_id) {
|
||||||
|
logger.warn("aos_task_create_failed", {
|
||||||
|
hasPayload: createPayload != null,
|
||||||
|
status: createResponse.status,
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: `AOS任务创建失败: ${createPayload?.message || createResponse.statusText}`,
|
error: `AOS任务创建失败: ${createPayload?.message || createResponse.statusText}`,
|
||||||
@ -368,6 +392,10 @@ async function processWithAosSegmentation(
|
|||||||
|
|
||||||
const pollPayload = await pollResponse.json().catch(() => null);
|
const pollPayload = await pollResponse.json().catch(() => null);
|
||||||
if (!pollResponse.ok || !pollPayload?.data) {
|
if (!pollResponse.ok || !pollPayload?.data) {
|
||||||
|
logger.warn("aos_task_poll_failed", {
|
||||||
|
hasPayload: pollPayload != null,
|
||||||
|
status: pollResponse.status,
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: `AOS任务查询失败: ${pollPayload?.message || pollResponse.statusText}`,
|
error: `AOS任务查询失败: ${pollPayload?.message || pollResponse.statusText}`,
|
||||||
@ -380,6 +408,10 @@ async function processWithAosSegmentation(
|
|||||||
const errMessage = String(pollPayload.data.err_message || pollPayload.data.err_info || "");
|
const errMessage = String(pollPayload.data.err_message || pollPayload.data.err_info || "");
|
||||||
|
|
||||||
if (state === 1 && imageUrl) {
|
if (state === 1 && imageUrl) {
|
||||||
|
logger.debug("aos_task_completed", {
|
||||||
|
pollCount,
|
||||||
|
taskId,
|
||||||
|
});
|
||||||
const imageResponse = await withTimeout(
|
const imageResponse = await withTimeout(
|
||||||
(signal) =>
|
(signal) =>
|
||||||
fetch(imageUrl, {
|
fetch(imageUrl, {
|
||||||
@ -390,6 +422,10 @@ async function processWithAosSegmentation(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!imageResponse.ok) {
|
if (!imageResponse.ok) {
|
||||||
|
logger.warn("aos_result_download_failed", {
|
||||||
|
status: imageResponse.status,
|
||||||
|
taskId,
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: `AOS结果下载失败: ${imageResponse.status} ${imageResponse.statusText}`,
|
error: `AOS结果下载失败: ${imageResponse.status} ${imageResponse.statusText}`,
|
||||||
@ -405,6 +441,11 @@ async function processWithAosSegmentation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state < 0) {
|
if (state < 0) {
|
||||||
|
logger.warn("aos_task_failed", {
|
||||||
|
hasErrorMessage: Boolean(errMessage),
|
||||||
|
state,
|
||||||
|
taskId,
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: `AOS任务失败: ${errMessage || stateDetail || pollPayload?.message || "状态异常"}`,
|
error: `AOS任务失败: ${errMessage || stateDetail || pollPayload?.message || "状态异常"}`,
|
||||||
@ -412,11 +453,13 @@ async function processWithAosSegmentation(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.warn("aos_task_timeout", { maxPollCount });
|
||||||
return { success: false, error: "AOS任务超时(最多60秒)" };
|
return { success: false, error: "AOS任务超时(最多60秒)" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error && error.name === "AbortError") {
|
if (error instanceof Error && error.name === "AbortError") {
|
||||||
return { success: false, error: "AOS请求超时,请稍后重试" };
|
return { success: false, error: "AOS请求超时,请稍后重试" };
|
||||||
}
|
}
|
||||||
|
logger.warn("aos_provider_request_error", error);
|
||||||
return { success: false, error: error instanceof Error ? error.message : "未知错误" };
|
return { success: false, error: error instanceof Error ? error.message : "未知错误" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -468,7 +511,7 @@ export async function fetchImageBufferFromUrl(imageUrl: string): Promise<Buffer>
|
|||||||
if (error instanceof RmbgServiceError) {
|
if (error instanceof RmbgServiceError) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
console.error("[RMBG] 获取图片失败:", error);
|
logger.warn("source_image_fetch_failed", error);
|
||||||
throw new RmbgServiceError("无法获取图片,请检查图片链接是否有效", 400);
|
throw new RmbgServiceError("无法获取图片,请检查图片链接是否有效", 400);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -488,7 +531,14 @@ export async function executeRmbgJob(
|
|||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
const { provider, config } = await getValidatedRmbgConfig();
|
const { provider, config } = await getValidatedRmbgConfig();
|
||||||
console.log(`[RMBG] Using provider: ${provider}`);
|
logger.info("job_started", {
|
||||||
|
hasChatId: Boolean(chatId),
|
||||||
|
hasMessageId: Boolean(messageId),
|
||||||
|
hasSourceImageUrl: Boolean(sourceImageUrl),
|
||||||
|
inputBytes: imageBuffer.length,
|
||||||
|
provider,
|
||||||
|
skipGallerySave,
|
||||||
|
});
|
||||||
|
|
||||||
let resultBuffer: Buffer;
|
let resultBuffer: Buffer;
|
||||||
let resultBase64: string;
|
let resultBase64: string;
|
||||||
@ -497,6 +547,10 @@ export async function executeRmbgJob(
|
|||||||
if (provider === "photoroom") {
|
if (provider === "photoroom") {
|
||||||
const result = await processWithPhotoRoom(imageBuffer, config.photoroomApiKey!);
|
const result = await processWithPhotoRoom(imageBuffer, config.photoroomApiKey!);
|
||||||
if (!result.success || !result.imageBuffer) {
|
if (!result.success || !result.imageBuffer) {
|
||||||
|
logger.warn("job_provider_failed", {
|
||||||
|
provider,
|
||||||
|
reasonLength: result.error?.length ?? 0,
|
||||||
|
});
|
||||||
throw new RmbgServiceError("抠图处理失败", 500, result.error);
|
throw new RmbgServiceError("抠图处理失败", 500, result.error);
|
||||||
}
|
}
|
||||||
resultBuffer = result.imageBuffer;
|
resultBuffer = result.imageBuffer;
|
||||||
@ -514,7 +568,16 @@ export async function executeRmbgJob(
|
|||||||
if (!result.success || !result.imageBuffer) {
|
if (!result.success || !result.imageBuffer) {
|
||||||
const detail = result.error || "AOS处理失败";
|
const detail = result.error || "AOS处理失败";
|
||||||
const friendlyMessage = getAosFriendlyErrorMessage(detail);
|
const friendlyMessage = getAosFriendlyErrorMessage(detail);
|
||||||
throw new RmbgServiceError("抠图处理失败", friendlyMessage ? 400 : 500, friendlyMessage || detail);
|
logger.warn("job_provider_failed", {
|
||||||
|
friendly: Boolean(friendlyMessage),
|
||||||
|
provider,
|
||||||
|
reasonLength: detail.length,
|
||||||
|
});
|
||||||
|
throw new RmbgServiceError(
|
||||||
|
"抠图处理失败",
|
||||||
|
friendlyMessage ? 400 : 500,
|
||||||
|
friendlyMessage || detail
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
resultBuffer = result.imageBuffer;
|
resultBuffer = result.imageBuffer;
|
||||||
@ -527,6 +590,10 @@ export async function executeRmbgJob(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!result.success || !result.image_base64) {
|
if (!result.success || !result.image_base64) {
|
||||||
|
logger.warn("job_provider_failed", {
|
||||||
|
provider,
|
||||||
|
reasonLength: result.error?.length ?? 0,
|
||||||
|
});
|
||||||
throw new RmbgServiceError("抠图处理失败", 500, result.error);
|
throw new RmbgServiceError("抠图处理失败", 500, result.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -575,7 +642,7 @@ export async function executeRmbgJob(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!saved) {
|
if (!saved) {
|
||||||
console.error("[RMBG] ❌ 保存到数据库失败:", saveError);
|
logger.error("gallery_save_failed", saveError);
|
||||||
throw new RmbgServiceError("图片处理成功但保存到相册失败,请稍后重试", 500);
|
throw new RmbgServiceError("图片处理成功但保存到相册失败,请稍后重试", 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -628,9 +695,17 @@ export async function executeRmbgJob(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (assetError) {
|
} catch (assetError) {
|
||||||
console.error("[RMBG] ⚠️ MediaAsset 注册失败:", assetError);
|
logger.warn("media_asset_registration_failed", assetError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info("job_completed", {
|
||||||
|
generatedImageId: savedImage.id,
|
||||||
|
mediaAssetId,
|
||||||
|
processingSeconds: Number(processingTime.toFixed(2)),
|
||||||
|
provider,
|
||||||
|
savedToGallery: true,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
image_base64: resultBase64,
|
image_base64: resultBase64,
|
||||||
thumbnail_url: thumbnailUrl,
|
thumbnail_url: thumbnailUrl,
|
||||||
@ -642,9 +717,15 @@ export async function executeRmbgJob(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("[RMBG] Skip gallery save by request flag");
|
logger.info("gallery_save_skipped", { provider });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info("job_completed", {
|
||||||
|
processingSeconds: Number(processingTime.toFixed(2)),
|
||||||
|
provider,
|
||||||
|
savedToGallery: false,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
image_base64: resultBase64,
|
image_base64: resultBase64,
|
||||||
thumbnail_url: thumbnailUrl,
|
thumbnail_url: thumbnailUrl,
|
||||||
|
|||||||
@ -24,6 +24,7 @@ const files = [
|
|||||||
"lib/ai/providers/gemini-image.ts",
|
"lib/ai/providers/gemini-image.ts",
|
||||||
"lib/ai/providers/volcano-vision.ts",
|
"lib/ai/providers/volcano-vision.ts",
|
||||||
"lib/ai/providers/qianwen-video.ts",
|
"lib/ai/providers/qianwen-video.ts",
|
||||||
|
"lib/ai/rmbg/service.ts",
|
||||||
"lib/ai/dynamic-provider.ts",
|
"lib/ai/dynamic-provider.ts",
|
||||||
"services/canvas/index.ts",
|
"services/canvas/index.ts",
|
||||||
"components/image-annotator.tsx",
|
"components/image-annotator.tsx",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user