From fadcbbc206a0240520fa5fa0560286282bb12157 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 26 May 2026 22:02:17 +0800 Subject: [PATCH] chore: expand canvas log hygiene coverage --- app/api/canvas/auto-title/route.ts | 14 +- app/api/canvas/detail-plan/route.ts | 32 ++- app/api/canvas/text-detect/route.ts | 10 +- app/api/scene-fusion/route.ts | 335 ++++++++++++++++------------ components/image-annotator.tsx | 115 ++++------ scripts/check-log-hygiene.mjs | 6 + services/canvas/index.ts | 30 +-- 7 files changed, 284 insertions(+), 258 deletions(-) diff --git a/app/api/canvas/auto-title/route.ts b/app/api/canvas/auto-title/route.ts index ae10c1c97..d92270b3f 100644 --- a/app/api/canvas/auto-title/route.ts +++ b/app/api/canvas/auto-title/route.ts @@ -1,25 +1,27 @@ import { NextRequest, NextResponse } from "next/server"; import { auth } from "@/app/(auth)/auth"; import { callQwen3Vl } from "@/lib/ai/qwen3-vl"; +import { createLogger } from "@/lib/observability/logger"; + +const logger = createLogger("canvas-auto-title"); export async function POST(request: NextRequest) { - console.log("[Auto-title] API called"); + logger.debug("request_received"); try { const session = await auth(); if (!session?.user?.id) { - console.log("[Auto-title] Unauthorized"); + logger.warn("unauthorized"); return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const { imageUrl } = await request.json(); - console.log("[Auto-title] Image URL:", imageUrl); if (!imageUrl) { return NextResponse.json({ error: "Image URL is required" }, { status: 400 }); } - console.log("[Auto-title] 使用硬编码模型: qwen3-vl"); + logger.debug("model_selected", { model: "qwen3-vl" }); // 构造 Prompt const prompt = "请用2-4个字概括这张图片的主体内容,作为文件标题。直接返回标题,不要包含任何标点符号或其他文字。"; @@ -35,11 +37,11 @@ export async function POST(request: NextRequest) { title = title.replace(/[\s\p{P}\p{S}]+/gu, ""); if (title.length > 4) title = title.slice(0, 4); if (title.length < 2) title = "新画布"; - console.log("[Auto-title] Generated title:", title, "model:", model); + logger.info("title_generated", { model, titleLength: title.length }); return NextResponse.json({ title }); } catch (error) { - console.error("[Auto-title] Error:", error); + logger.error("auto_title_failed", error); // 失败时返回默认标题,不报错中断流程 return NextResponse.json({ title: "新画布" }); } diff --git a/app/api/canvas/detail-plan/route.ts b/app/api/canvas/detail-plan/route.ts index 81957434a..e1fe91534 100644 --- a/app/api/canvas/detail-plan/route.ts +++ b/app/api/canvas/detail-plan/route.ts @@ -17,6 +17,7 @@ import { type DetailKimiGenerateOptions, } from "@/lib/ai/detail-agent-kimi-profile"; import { inlineVisionImages } from "@/lib/ai/vision-inline"; +import { createLogger } from "@/lib/observability/logger"; import { filterReachableRemoteUrls } from "@/lib/net/url-access"; import { buildDetailUiProtocol, @@ -47,6 +48,7 @@ const DETAIL_PLAN_INLINE_TIMEOUT_MS = 4000; const DETAIL_PLAN_INLINE_RETRIES = 1; const DETAIL_PLAN_MAIN_TIMEOUT_MS = 45000; const DETAIL_PLAN_PREFLIGHT_TIMEOUT_MS = 10000; +const logger = createLogger("canvas-detail-plan"); function isAbortError(error: unknown): boolean { const target = error as { @@ -1774,7 +1776,7 @@ async function runDualDirectorPlanPreflight(params: { candidateScreens, }; } catch (error) { - console.error("[Canvas Detail Plan] preflight error:", error); + logger.warn("preflight_failed", error); return defaultResult; } } @@ -1808,19 +1810,17 @@ export async function POST(request: Request) { const bundleImageUrls = bundleImageCheck.kept.slice(0, 3); const modelImageUrl = modelImageCheck.kept[0] || ""; if (referenceImageCheck.dropped.length > 0) { - console.warn("[Canvas Detail Plan] Dropping unreachable referenceImages:", { + logger.warn("reference_images_dropped", { count: referenceImageCheck.dropped.length, - urls: referenceImageCheck.dropped.slice(0, 6), }); } if (bundleImageCheck.dropped.length > 0) { - console.warn("[Canvas Detail Plan] Dropping unreachable bundleImageUrls:", { + logger.warn("bundle_images_dropped", { count: bundleImageCheck.dropped.length, - urls: bundleImageCheck.dropped.slice(0, 3), }); } if (modelImageUrlRaw && !modelImageUrl) { - console.warn("[Canvas Detail Plan] modelImageUrl unreachable, ignore this run:", modelImageUrlRaw); + logger.warn("model_image_dropped"); } const effectiveReferenceImageNotes = referenceImages.length === referenceImagesRaw.length ? referenceImageNotes : []; @@ -1935,7 +1935,7 @@ export async function POST(request: Request) { isLocalMoonshot: Boolean(localDetailAgentResolved), }); - console.log("[Canvas Detail Plan] Planner model selected:", { + logger.info("planner_model_selected", { id: selectedModelRef.id, displayName: selectedModelRef.displayName, modelId: selectedModelRef.modelId, @@ -2030,10 +2030,9 @@ export async function POST(request: Request) { } } catch (error) { if (isReferenceImageDownloadError(error) || isTransientNetworkError(error)) { - console.warn( - "[Canvas Detail Plan] Vision input preprocessing failed, retry without reference images:", - error - ); + logger.warn("vision_input_preprocessing_failed", error, { + referenceImageCount: referenceImages.length, + }); imageInputs = []; } else { throw error; @@ -2125,10 +2124,9 @@ export async function POST(request: Request) { if (!shouldRetryWithoutImages) { throw firstError; } - console.warn( - "[Canvas Detail Plan] Primary planning with reference images failed, retrying without reference images:", - firstError - ); + logger.warn("planner_retry_without_reference_images", firstError, { + imageInputCount: imageInputs.length, + }); const retryResult = await runPlanner([]); text = String(retryResult?.text || ""); } @@ -2141,7 +2139,7 @@ export async function POST(request: Request) { isTransientNetworkError(error) || /operationtimeouterror|timed out|timeout/i.test(message) ) { - console.error("[Canvas Detail Plan] Primary planning aborted, fallback applied:", error); + logger.error("primary_planning_fallback_applied", error); const fallbackReason = /invalid scheme/i.test(message) ? "规划模型图片输入协议不兼容,已回退到稳定分镜模板。" : isReferenceImageDownloadError(error) @@ -2384,7 +2382,7 @@ export async function POST(request: Request) { return NextResponse.json({ success: true, data: response }); } catch (error) { - console.error("[Canvas Detail Plan] Error:", error); + logger.error("detail_plan_failed", error); return NextResponse.json( { success: false, error: error instanceof Error ? error.message : "详情规划失败" }, { status: 500 }, diff --git a/app/api/canvas/text-detect/route.ts b/app/api/canvas/text-detect/route.ts index 8870a5e54..cd66f6049 100644 --- a/app/api/canvas/text-detect/route.ts +++ b/app/api/canvas/text-detect/route.ts @@ -2,6 +2,7 @@ import { NextResponse } from "next/server"; import { z } from "zod"; import { auth } from "@/app/(auth)/auth"; import { callQwen3Vl } from "@/lib/ai/qwen3-vl"; +import { createLogger } from "@/lib/observability/logger"; import sharp from "sharp"; export const runtime = "nodejs"; @@ -13,6 +14,7 @@ const requestSchema = z.object({ const OCR_IMAGE_FETCH_TIMEOUT_MS = 15000; const OCR_IMAGE_MAX_SIDE = 1400; +const logger = createLogger("canvas-text-detect"); interface OcrItem { id: string; @@ -145,7 +147,7 @@ export async function POST(request: Request) { return NextResponse.json({ success: false, error: "仅支持 URL 图片,不允许 base64 传输" }, { status: 400 }); } - console.log("[Canvas Text Detect] 使用硬编码模型: qwen3-vl"); + logger.debug("model_selected", { model: "qwen3-vl" }); const prompt = [ "你是图片文字识别助手。", "请识别图片中可见文字并按阅读顺序输出 JSON。", @@ -172,7 +174,9 @@ export async function POST(request: Request) { throw error; } - console.warn("[Canvas Text Detect] URL 拉图超时,切换 data URL 兜底"); + logger.warn("vision_url_fallback", { + reason: error instanceof Error ? error.message : "unknown", + }); const dataUrl = await buildVisionDataUrl(imageUrl); const fallbackResult = await callQwen3Vl({ imageUrl: dataUrl, @@ -205,7 +209,7 @@ export async function POST(request: Request) { }, }); } catch (error) { - console.error("[Canvas Text Detect] Error:", error); + logger.error("text_detect_failed", error); return NextResponse.json( { success: false, diff --git a/app/api/scene-fusion/route.ts b/app/api/scene-fusion/route.ts index fb9cc21de..3ee33a733 100644 --- a/app/api/scene-fusion/route.ts +++ b/app/api/scene-fusion/route.ts @@ -32,6 +32,7 @@ import { type SceneFusionImageGenConfig, } from "@/lib/scene-fusion/output-capabilities"; import { acquireCanvasImageModelSlot } from "@/lib/canvas/image-concurrency"; +import { createLogger } from "@/lib/observability/logger"; // 请求体 Schema const sceneFusionRequestSchema = z.object({ @@ -70,6 +71,7 @@ const SCENE_FUSION_RANDOM_BG_PROMPT_TAIL = "硬性要求:背景必须与原图明显不同(颜色、结构、元素至少两项显著变化),禁止保留原背景纹理或直接回传原图;仅保留产品主体并保证主体结构、轮廓比例、部件关系和边缘完整自然,允许为新背景调整朝向、角度和透视。"; let sceneFusionRoundRobinCursor = 0; type SceneFusionModelCooldown = Map; +const logger = createLogger("scene-fusion"); interface SceneFusionModelCandidate { model: { @@ -304,8 +306,7 @@ async function getAverageHashBits( hashCache.set(imageUrl, hashBits); return hashBits; } catch (error) { - console.warn("[SceneFusion] Echo hash compute skipped:", { - imageUrl: imageUrl.slice(0, 120), + logger.warn("echo_hash_compute_skipped", { error: error instanceof Error ? error.message : "unknown", }); return null; @@ -357,9 +358,12 @@ async function detectEchoedProductResult( const distance = hammingDistanceBits(productHash, generatedHash); if (distance <= SCENE_FUSION_ECHO_HASH_THRESHOLD) { - console.warn( - `[SceneFusion][${params.requestId}] Echo-guard hit: scene#${params.sceneIndex + 1}, model=${params.modelName}, distance=${distance}` - ); + logger.warn("echo_guard_hit", { + requestId: params.requestId, + sceneIndex: params.sceneIndex + 1, + modelName: params.modelName, + distance, + }); return { tooSimilar: true, reason: "hash_too_close", @@ -603,7 +607,8 @@ async function reviewSceneFusionResult(params: { suspectedEcho !== true && score >= 0.5; - console.log(`[SceneFusion][${params.requestId}] Review result:`, { + logger.info("review_result", { + requestId: params.requestId, sceneIndex: params.sceneIndex + 1, candidate: params.modelName, passed, @@ -621,7 +626,8 @@ async function reviewSceneFusionResult(params: { model, }; } catch (error) { - console.warn(`[SceneFusion][${params.requestId}] Review skipped:`, { + logger.warn("review_skipped", { + requestId: params.requestId, sceneIndex: params.sceneIndex + 1, candidate: params.modelName, error: error instanceof Error ? error.message : "unknown", @@ -745,10 +751,10 @@ async function enrichRandomBackgroundScenesWithAgent(params: { scene.customPrompt = mergeCustomPrompt(scene.customPrompt, `场景建议:${generatedPrompt}`); } } catch (error) { - console.warn( - `[SceneFusion][${params.requestId}] Random scene prompt enhance skipped: scene=${scene.id}`, - error - ); + logger.warn("random_prompt_enhance_skipped", error, { + requestId: params.requestId, + sceneId: scene.id, + }); } } }); @@ -809,7 +815,7 @@ async function enrichSceneLabelsWithAgent(params: { meta.productModel = productDetected.model; } } catch (error) { - console.warn(`[SceneFusion][${params.requestId}] Product detect skipped:`, error); + logger.warn("product_detect_skipped", error, { requestId: params.requestId }); } const unresolvedIndexes = clonedScenes @@ -858,10 +864,10 @@ async function enrichSceneLabelsWithAgent(params: { metaScene.model = targetDetected.model; } } catch (error) { - console.warn( - `[SceneFusion][${params.requestId}] Scene target detect skipped: scene=${scene.id}`, - error - ); + logger.warn("scene_target_detect_skipped", error, { + requestId: params.requestId, + sceneId: scene.id, + }); } } }); @@ -957,7 +963,7 @@ async function getSceneFusionConfig() { }; } } catch (error) { - console.error("[SceneFusion] Failed to load config:", error); + logger.error("config_load_failed", error); } return { @@ -1000,9 +1006,10 @@ async function preprocessProductImageWithRmbg( const candidateUrls = buildPreRmbgCandidateUrls(request); let lastReason = "no_candidate_url"; - console.log( - `[SceneFusion][${requestId}] Pre-RMBG start: ${productImageUrl.substring(0, 120)}..., candidates=${candidateUrls.join(" | ")}` - ); + logger.info("pre_rmbg_started", { + requestId, + candidateCount: candidateUrls.length, + }); for (const rmbgUrl of candidateUrls) { const controller = new AbortController(); @@ -1026,7 +1033,8 @@ async function preprocessProductImageWithRmbg( if (!response.ok) { lastReason = `request_failed_${response.status}`; - console.warn(`[SceneFusion][${requestId}] Pre-RMBG candidate failed: ${rmbgUrl}`, { + logger.warn("pre_rmbg_candidate_failed", { + requestId, status: response.status, detail: payload?.detail || payload?.error || "unknown", }); @@ -1036,7 +1044,8 @@ async function preprocessProductImageWithRmbg( const cleanedUrl = String(payload?.original_url || payload?.thumbnail_url || "").trim(); if (!cleanedUrl || cleanedUrl === productImageUrl) { lastReason = cleanedUrl ? "same_as_source" : "empty_result"; - console.warn(`[SceneFusion][${requestId}] Pre-RMBG candidate invalid result: ${rmbgUrl}`, { + logger.warn("pre_rmbg_candidate_invalid", { + requestId, reason: lastReason, }); continue; @@ -1049,7 +1058,7 @@ async function preprocessProductImageWithRmbg( }; } catch (error) { lastReason = error instanceof Error ? error.message : "exception"; - console.warn(`[SceneFusion][${requestId}] Pre-RMBG candidate exception: ${rmbgUrl}`, error); + logger.warn("pre_rmbg_candidate_exception", error, { requestId }); } finally { clearTimeout(timeoutId); } @@ -1073,7 +1082,7 @@ async function createThumbnail(buffer: Buffer): Promise { .toBuffer(); return thumbnailBuffer; } catch (error) { - console.error("[SceneFusion] Failed to create thumbnail:", error); + logger.error("thumbnail_create_failed", error); return buffer; } } @@ -1098,10 +1107,10 @@ async function uploadBase64ToS3(base64Data: string, prefix: string = "scene-fusi const filename = generateFilename(`${prefix}-ref`, "png"); const result = await uploadFile(filename, buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)); - console.log(`[SceneFusion] Uploaded reference image to: ${result.url}`); + logger.info("reference_image_uploaded", { prefix, bytes: buffer.byteLength }); return result.url; } catch (error) { - console.error("[SceneFusion] Failed to upload reference image:", error); + logger.error("reference_image_upload_failed", error, { prefix }); throw error; } } @@ -1114,15 +1123,15 @@ async function downloadAndUploadToS3( prefix: string = "scene-fusion" ): Promise<{ thumbnail: string; original: string }> { try { - console.log("[SceneFusion] Downloading image from:", imageUrl.substring(0, 100) + "..."); - + logger.debug("download_upload_started", { prefix }); + const response = await fetch(imageUrl); if (!response.ok) { throw new Error(`Failed to download image: ${response.status}`); } const buffer = Buffer.from(await response.arrayBuffer()); - console.log(`[SceneFusion] Downloaded image, size: ${buffer.length} bytes`); + logger.debug("image_downloaded", { prefix, bytes: buffer.length }); // 创建缩略图 const thumbnailBuffer = await createThumbnail(buffer); @@ -1130,20 +1139,20 @@ async function downloadAndUploadToS3( // 上传原图 const originalFilename = generateFilename(`${prefix}-orig`, "png"); const originalResult = await uploadFile(originalFilename, buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)); - console.log(`[SceneFusion] Uploaded original to: ${originalResult.url}`); + logger.debug("original_uploaded", { prefix, bytes: buffer.byteLength }); // 上传缩略图 const thumbnailFilename = generateFilename(`${prefix}-thumb`, "jpg"); const thumbnailArrayBuffer = new Uint8Array(thumbnailBuffer).buffer; const thumbnailResult = await uploadFile(thumbnailFilename, thumbnailArrayBuffer); - console.log(`[SceneFusion] Uploaded thumbnail to: ${thumbnailResult.url}`); + logger.debug("thumbnail_uploaded", { prefix, bytes: thumbnailBuffer.byteLength }); return { original: originalResult.url, thumbnail: thumbnailResult.url, }; } catch (error) { - console.error("[SceneFusion] Failed to download/upload image:", error); + logger.error("download_upload_failed", error, { prefix }); throw error; } } @@ -1229,7 +1238,7 @@ async function saveSceneFusionImageRecord(params: { } } } catch (assetError) { - console.error("[SceneFusion] ⚠️ MediaAsset 注册失败:", assetError); + logger.error("media_asset_register_failed", assetError); } } @@ -1318,13 +1327,13 @@ async function callAlibabaImageAPI( }, }; - console.log(`[SceneFusion][${modelId}] Request Debug: - - Index: Scene ${sceneImageUrl.substring(sceneImageUrl.length-15)} - - Product URL: ${productImageUrl} - - Scene URL: ${sceneImageUrl} - - Size: ${size || 'auto'} - - Prompt: ${prompt} - `); + logger.debug("alibaba_request_prepared", { + modelId, + size: size || "auto", + productImageCount: productImageUrl ? 1 : 0, + sceneImageCount: sceneImageUrl ? 1 : 0, + promptLength: prompt.length, + }); const response = await fetch(baseURL, { method: "POST", @@ -1337,23 +1346,35 @@ async function callAlibabaImageAPI( const data = await response.json(); - // 打印完整响应用于调试 - console.log(`[SceneFusion][${modelId}] Full API Response:`, JSON.stringify(data, null, 2)); + logger.debug("alibaba_response_received", { + modelId, + ok: response.ok, + code: data?.code ?? null, + hasOutput: Boolean(data?.output), + }); if (!response.ok || data.code) { - console.error("[SceneFusion] API Error Response:", JSON.stringify(data, null, 2)); + logger.error("alibaba_response_error", { + modelId, + status: response.status, + code: data?.code ?? null, + message: data?.message ?? null, + }); throw new Error(data.message || (data.code ? `Error ${data.code}: ${data.message}` : `API request failed: ${response.status}`)); } // 提取生成的图片 URL const choices = data.output?.choices; if (!choices || choices.length === 0) { - console.error("[SceneFusion] No choices in response, output:", data.output); + logger.error("alibaba_no_choices", { modelId, hasOutput: Boolean(data?.output) }); throw new Error("No images in response"); } const imageContent = choices[0]?.message?.content; - console.log(`[SceneFusion] Image content array:`, JSON.stringify(imageContent, null, 2)); + logger.debug("alibaba_image_content_received", { + modelId, + contentCount: Array.isArray(imageContent) ? imageContent.length : 0, + }); if (!imageContent || imageContent.length === 0) { throw new Error("No image content in response"); @@ -1364,25 +1385,25 @@ async function callAlibabaImageAPI( for (const item of imageContent) { if (item.image) { generatedImageUrl = item.image; - console.log(`[SceneFusion] Found image URL in content item`); + logger.debug("alibaba_image_url_found", { modelId }); break; } } if (!generatedImageUrl) { - console.error("[SceneFusion] No image URL found in content:", imageContent); + logger.error("alibaba_image_url_missing", { + modelId, + contentCount: Array.isArray(imageContent) ? imageContent.length : 0, + }); throw new Error("No image URL in response"); } // 检查返回的图片 URL 是否与输入相同(可能是 API 返回了原图) if (generatedImageUrl === productImageUrl || generatedImageUrl === sceneImageUrl) { - console.warn(`[SceneFusion] ⚠️ WARNING: API returned same URL as input!`); - console.warn(`[SceneFusion] - Generated URL: ${generatedImageUrl}`); - console.warn(`[SceneFusion] - Product URL: ${productImageUrl}`); - console.warn(`[SceneFusion] - Scene URL: ${sceneImageUrl}`); + logger.warn("alibaba_returned_input_url", { modelId }); } - console.log("[SceneFusion] Generated image URL:", generatedImageUrl.substring(0, 100) + "..."); + logger.debug("alibaba_image_generated", { modelId }); return generatedImageUrl; } @@ -1468,8 +1489,11 @@ async function processSceneWithProvider( sceneMode ); - console.log(`[SceneFusion] Built prompt: ${prompt}`); - console.log(`[SceneFusion] Provider type: ${provider.type}`); + logger.debug("provider_prompt_built", { + providerType: provider.type, + promptLength: prompt.length, + sceneMode, + }); // 根据 provider 类型选择不同的处理逻辑 switch (provider.type) { @@ -1541,7 +1565,7 @@ async function processWithGemini( aspectRatio?: string, imageSize?: string ): Promise<{ thumbnail: string; original: string; prompt: string }> { - console.log(`[SceneFusion][Gemini] Processing with model: ${modelId}`); + logger.info("provider_processing", { providerType: "gemini", modelId }); // 创建 Gemini provider const geminiProvider = createGeminiImage({ @@ -1589,7 +1613,7 @@ async function processWithRuxaImage( aspectRatio?: string, imageSize?: string ): Promise<{ thumbnail: string; original: string; prompt: string }> { - console.log(`[SceneFusion][RUXA] Processing with model: ${modelId}`); + logger.info("provider_processing", { providerType: "ruxa-image", modelId }); const ruxaProvider = createRuxaImage({ baseURL: provider.baseUrl, @@ -1629,7 +1653,7 @@ async function processWithFalImage( aspectRatio?: string, imageSize?: string ): Promise<{ thumbnail: string; original: string; prompt: string }> { - console.log(`[SceneFusion][FAL] Processing with model: ${modelId}`); + logger.info("provider_processing", { providerType: "fal-image", modelId }); const falProvider = createFalImage({ baseURL: provider.baseUrl, @@ -1669,7 +1693,7 @@ async function processWithOpenAIImage( aspectRatio?: string, imageSize?: string ): Promise<{ thumbnail: string; original: string; prompt: string }> { - console.log(`[SceneFusion][OpenAI Image] Processing with model: ${modelId}`); + logger.info("provider_processing", { providerType: "openai-image", modelId }); const openAIImageProvider = createOpenAIImage({ baseURL: provider.baseUrl, @@ -1712,7 +1736,7 @@ async function processWithVolcanoVision( aspectRatio?: string, imageSize?: string ): Promise<{ thumbnail: string; original: string; prompt: string }> { - console.log(`[SceneFusion][Volcano] Processing with model: ${modelId}`); + logger.info("provider_processing", { providerType: "volcano-vision", modelId }); // 创建 Volcano Vision provider const volcanoProvider = createVolcanoVision({ @@ -1753,7 +1777,7 @@ async function processWithVolcanoVision( * 格式: ![thumb|||originalUrl|||size](thumbnailUrl) */ function parseImageFromMarkdown(markdown: string): { thumbnail: string; original: string } { - console.log(`[SceneFusion] Parsing markdown: ${markdown.substring(0, 200)}...`); + logger.debug("markdown_parse_started", { markdownLength: markdown.length }); // 匹配 ![thumb|||originalUrl|||size](thumbnailUrl) 格式 const match = markdown.match(/!\[thumb\|\|\|([^|]+)\|\|\|[^\]]*\]\(([^)]+)\)/); @@ -1805,7 +1829,7 @@ async function processScene( sceneMode ); - console.log(`[SceneFusion] Built prompt: ${prompt}`); + logger.debug("legacy_prompt_built", { promptLength: prompt.length, sceneMode }); // 调用阿里生图 API const generatedImageUrl = await callAlibabaImageAPI( @@ -1890,14 +1914,15 @@ async function processSceneWithDiversity( const attemptCandidates = orderedHealthyCandidates.length > 0 ? orderedHealthyCandidates : orderedFallbackCandidates; - console.log( - `[SceneFusion][${params.requestId}] Scene#${params.sceneIndex + 1} candidate order: ${attemptCandidates - .map( - (candidate) => - `${candidate.model.displayName}/${candidate.provider.type}/score=${getSceneFusionCandidatePriority(candidate)}` - ) - .join(" -> ")}` - ); + logger.debug("candidate_order_resolved", { + requestId: params.requestId, + sceneIndex: params.sceneIndex + 1, + candidates: attemptCandidates.map((candidate) => ({ + modelId: candidate.model.modelId, + providerType: candidate.provider.type, + score: getSceneFusionCandidatePriority(candidate), + })), + }); const failureReasons: string[] = []; @@ -1909,9 +1934,12 @@ async function processSceneWithDiversity( imageSize: params.imageSize, }); if (normalizedOutputParams.changed) { - console.log( - `[SceneFusion][${params.requestId}] Candidate ${candidate.model.displayName} output params normalized: ${normalizedOutputParams.changes.join(", ")}` - ); + logger.info("candidate_output_params_normalized", { + requestId: params.requestId, + sceneIndex: params.sceneIndex + 1, + modelId: candidate.model.modelId, + changes: normalizedOutputParams.changes, + }); } const modelSlot = await acquireCanvasImageModelSlot({ @@ -2000,17 +2028,19 @@ async function processSceneWithDiversity( markSceneFusionCandidateFailed(candidate, params.modelCooldown); // Batch processing can fail because of per-image inputs, review gates, or echo checks. // Keep these failures local to this request so they do not auto-disable shared models. - const logCandidateFailure = isSceneFusionReviewRejectError(errorMessage) - ? console.warn - : console.error; - logCandidateFailure("[SceneFusion] Candidate failed, try next:", { + const failureMeta = { requestId: params.requestId, sceneIndex: params.sceneIndex, attempt: attemptIndex + 1, model: candidate.model.modelId, provider: candidate.provider.type, error: errorMessage, - }); + }; + if (isSceneFusionReviewRejectError(errorMessage)) { + logger.warn("candidate_review_rejected", failureMeta); + } else { + logger.error("candidate_failed", failureMeta); + } } } @@ -2060,7 +2090,16 @@ export async function POST(request: Request) { // 计算输出尺寸 const size = calculateSize(aspectRatio || "auto", imageSize || "2K"); - console.log(`[SceneFusion] Output settings: aspectRatio=${aspectRatio || 'auto'}, imageSize=${imageSize || '2K'}, calculated size=${size || 'auto'}`); + logger.info("request_received", { + requestId, + aspectRatio: aspectRatio || "auto", + imageSize: imageSize || "2K", + size: size || "auto", + sceneCount: scenes.length, + sceneMode: resolvedSceneMode, + hasProductName: Boolean(productName), + preferredModelId: config.modelId || null, + }); // 检查场景数量限制 const effectiveMaxScenes = Math.min(config.maxScenes, SCENE_FUSION_MAX_ITEMS); @@ -2071,19 +2110,11 @@ export async function POST(request: Request) { ); } - console.log(`[SceneFusion] Processing ${scenes.length} scenes for user ${session.user.id}`); - console.log(`[SceneFusion] Product name: ${productName || '(not specified)'}`); - console.log(`[SceneFusion] Scene mode: ${resolvedSceneMode}`); - console.log(`[SceneFusion] Using default prompt: ${config.defaultPrompt.substring(0, 50)}...`); - console.log(`[SceneFusion] Preferred model ID: ${config.modelId || '(none)'}`); - // 4. 获取生图模型池(固定多样性模式) const allModels = await getEnabledAiModelsWithProvider(); const candidateModels = buildSceneFusionCandidatePool(allModels, config.modelId); if (config.modelId && !candidateModels.some((candidate) => candidate.model.id === config.modelId)) { - console.warn( - `[SceneFusion] Preferred model ${config.modelId} not available, fallback to full diversity pool` - ); + logger.warn("preferred_model_unavailable", { requestId, modelId: config.modelId }); } if (candidateModels.length === 0) { @@ -2093,37 +2124,39 @@ export async function POST(request: Request) { ); } - console.log( - `[SceneFusion] Diversity candidates: ${candidateModels - .map((candidate) => `${candidate.model.displayName}(${candidate.provider.type})`) - .join(", ")}` - ); + logger.info("candidate_pool_resolved", { + requestId, + candidateCount: candidateModels.length, + providers: [...new Set(candidateModels.map((candidate) => candidate.provider.type))], + }); const unsupportedCandidates = candidateModels.filter( (candidate) => !canProcessSceneFusionProviderType(candidate.provider.type) ); if (unsupportedCandidates.length > 0) { - console.warn( - `[SceneFusion] Skip unsupported providers for scene fusion: ${unsupportedCandidates - .map((candidate) => `${candidate.model.displayName}(${candidate.provider.type})`) - .join(", ")}` - ); + logger.warn("unsupported_candidates_present", { + requestId, + count: unsupportedCandidates.length, + providers: [...new Set(unsupportedCandidates.map((candidate) => candidate.provider.type))], + }); } const nonVisionCandidates = candidateModels.filter( (candidate) => !candidate.model.supportsVision ); if (nonVisionCandidates.length > 0) { - console.warn( - `[SceneFusion] Models without supportsVision are included for failover diversity: ${nonVisionCandidates - .map((candidate) => `${candidate.model.displayName}(${candidate.provider.type})`) - .join(", ")}` - ); + logger.warn("non_vision_candidates_present", { + requestId, + count: nonVisionCandidates.length, + }); } const requestOffset = getNextSceneFusionRoundRobinOffset( Math.max(1, candidateModels.length) ); - console.log( - `[SceneFusion][${requestId}] Round-robin offset=${requestOffset}, scenes=${scenes.length}, candidates=${candidateModels.length}` - ); + logger.debug("round_robin_offset_resolved", { + requestId, + requestOffset, + sceneCount: scenes.length, + candidateCount: candidateModels.length, + }); // 5. 预处理:将 Base64 图片上传到 S3 let productImageUrl = productImage; @@ -2138,9 +2171,12 @@ export async function POST(request: Request) { shouldForcePreRmbg ? true : config.preRmbgEnabled ); const fusionProductImageUrl = preprocessedProduct.productImageUrl; - console.log( - `[SceneFusion][${requestId}] Pre-RMBG result: used=${preprocessedProduct.usedPreRmbg}, provider=${preprocessedProduct.provider || "-"}, reason=${preprocessedProduct.reason || "-"}` - ); + logger.info("pre_rmbg_resolved", { + requestId, + used: preprocessedProduct.usedPreRmbg, + provider: preprocessedProduct.provider || null, + reason: preprocessedProduct.reason || null, + }); const processedScenes = await Promise.all( scenes.map(async (scene) => { @@ -2175,17 +2211,18 @@ export async function POST(request: Request) { const imageHashCache = new Map(); const modelCooldown: SceneFusionModelCooldown = new Map(); - console.log( - `[SceneFusion][${requestId}] Agent labels: product=${resolvedProductName || "(none)"}; targets=${resolvedScenes - .map((scene) => `${scene.id}:${scene.targetName || "-"}`) - .join(", ")}` - ); + logger.info("agent_labels_resolved", { + requestId, + hasProductName: Boolean(resolvedProductName), + targetCount: resolvedScenes.filter((scene) => Boolean(scene.targetName)).length, + sceneCount: resolvedScenes.length, + }); if (resolvedSceneMode === "random-bg") { - console.log( - `[SceneFusion][${requestId}] Random-bg prompts: ${resolvedScenes - .map((scene) => `${scene.id}:${(scene.customPrompt || "").slice(0, 80) || "-"}`) - .join(" | ")}` - ); + logger.debug("random_bg_prompts_resolved", { + requestId, + sceneCount: resolvedScenes.length, + promptCount: resolvedScenes.filter((scene) => Boolean(scene.customPrompt)).length, + }); } // 6. 创建流式响应,每完成一张图就立即发送给前端 @@ -2199,7 +2236,7 @@ export async function POST(request: Request) { try { controller.close(); } catch (closeError) { - console.warn("[SceneFusion] Stream close skipped:", closeError); + logger.warn("stream_close_skipped", closeError, { requestId }); } }; const enqueue = (payload: Record) => { @@ -2209,7 +2246,7 @@ export async function POST(request: Request) { return true; } catch (enqueueError) { streamClosed = true; - console.warn("[SceneFusion] Stream enqueue skipped:", enqueueError); + logger.warn("stream_enqueue_skipped", enqueueError, { requestId }); return false; } }; @@ -2258,27 +2295,16 @@ export async function POST(request: Request) { const resolvedMetaScene = enriched.meta.sceneTargets.find((item) => item.id === resolvedScene.id) || null; - console.log( - `[SceneFusion] ========== Processing scene ${currentIndex + 1}/${processedScenes.length} ==========` - ); - console.log(`[SceneFusion] Scene ID: ${scene.id}`); - console.log( - `[SceneFusion] Product Image URL: ${fusionProductImageUrl.substring(0, 80)}...` - ); - console.log( - `[SceneFusion] Scene Image URL: ${scene.imageUrl.substring(0, 80)}...` - ); - console.log( - `[SceneFusion] Target Name: ${resolvedScene.targetName || "(not specified)"}` - ); - if (resolvedMetaScene?.positionHint || resolvedMetaScene?.replaceCue) { - console.log( - `[SceneFusion] Target Hints: position=${resolvedMetaScene?.positionHint || "-"}, cue=${resolvedMetaScene?.replaceCue || "-"}` - ); - } - console.log( - `[SceneFusion] Custom Prompt: ${resolvedScene.customPrompt || "(none)"}` - ); + logger.info("scene_processing_started", { + requestId, + sceneId: scene.id, + sceneIndex: currentIndex + 1, + total: processedScenes.length, + hasTargetName: Boolean(resolvedScene.targetName), + hasPositionHint: Boolean(resolvedMetaScene?.positionHint), + hasReplaceCue: Boolean(resolvedMetaScene?.replaceCue), + hasCustomPrompt: Boolean(resolvedScene.customPrompt), + }); try { const { result, candidate, attempts } = await processSceneWithDiversity({ @@ -2333,9 +2359,13 @@ export async function POST(request: Request) { modelId: candidate.model.modelId, sourceImageUrls: [fusionProductImageUrl, resolvedScene.imageUrl], }); - console.log( - `[SceneFusion] ✅ Saved image to gallery: ${result.original.substring(0, 80)}...` - ); + logger.info("scene_saved", { + requestId, + sceneId: scene.id, + modelId: candidate.model.modelId, + providerType: candidate.provider.type, + attempts, + }); successCount += 1; if (!enqueue({ @@ -2356,7 +2386,11 @@ export async function POST(request: Request) { break; } } catch (error) { - console.error(`[SceneFusion] Failed to process scene ${scene.id}:`, error); + logger.error("scene_processing_failed", error, { + requestId, + sceneId: scene.id, + sceneIndex: currentIndex + 1, + }); failCount += 1; if (!enqueue({ type: "result", @@ -2375,7 +2409,12 @@ export async function POST(request: Request) { await Promise.all(workers); // 发送完成消息 - console.log(`[SceneFusion] Completed: ${successCount} success, ${failCount} failed`); + logger.info("request_completed", { + requestId, + total: processedScenes.length, + success: successCount, + failed: failCount, + }); enqueue({ type: "done", summary: { @@ -2386,7 +2425,7 @@ export async function POST(request: Request) { }); } catch (error) { - console.error("[SceneFusion] Stream error:", error); + logger.error("stream_failed", error, { requestId }); // 发送错误消息 if (!streamClosed) { enqueue({ @@ -2408,7 +2447,7 @@ export async function POST(request: Request) { }, }); } catch (error) { - console.error("[SceneFusion] Unexpected error:", error); + logger.error("request_failed", error); return NextResponse.json( { error: error instanceof Error ? error.message : "处理失败" }, { status: 500 } diff --git a/components/image-annotator.tsx b/components/image-annotator.tsx index 6dfc433c2..bd67d8dc6 100644 --- a/components/image-annotator.tsx +++ b/components/image-annotator.tsx @@ -55,6 +55,7 @@ import { import { requestLongTaskResult } from "@/lib/client/long-task"; import { fetchUploadWithRetry } from "@/lib/client/upload-with-retry"; import { buildImageProxyUrl } from "@/lib/image-delivery"; +import { createClientLogger } from "@/lib/observability/client-logger"; import { cn } from "@/lib/utils"; import { AngleEditDialog } from "./angle-edit-dialog"; import { SceneFusionView } from "./scene-fusion-view"; @@ -64,6 +65,8 @@ import { Slider } from "./ui/slider"; type Tool = "pen" | "arrow" | "circle" | "text"; +const logger = createClientLogger("image-annotator"); + type DrawingAction = { type: Tool; color: string; @@ -315,17 +318,14 @@ export function ImageAnnotator({ const localImage = await localPreviewPromise; setImage(localImage); } catch (previewError) { - console.warn( - "[ImageAnnotator] Local preview failed, fallback to uploaded url:", - previewError - ); + logger.warn("local_preview_failed", previewError); const uploadedImage = await loadImageFromSource(uploadResult.url); setImage(uploadedImage); } toast.success("图片上传成功"); } catch (error) { - console.error("[ImageAnnotator] 上传图片失败:", error); + logger.error("upload_failed", error); const message = error instanceof Error ? error.message : "上传失败,请重试"; toast.error(message); @@ -517,7 +517,7 @@ export function ImageAnnotator({ if (data.siteLogoDark) setSiteLogoDark(data.siteLogoDark); }) .catch((err) => { - console.error("[ImageAnnotator] Failed to fetch site settings:", err); + logger.warn("site_settings_fetch_failed", err); }); }, []); @@ -559,7 +559,7 @@ export function ImageAnnotator({ setSelectedAspectRatio(null); } catch (error) { if (cancelled) return; - console.error("初始化图片加载失败:", error); + logger.error("initial_image_load_failed", error); setImage(null); setIsLoading(false); toast.error("当前图片加载失败,请重试"); @@ -892,9 +892,8 @@ export function ImageAnnotator({ setIsRemovingBg(true); try { // 使用原图URL获取原图数据,而不是canvas的预览图 - console.log("[抠图] 使用原图URL获取数据:", originalImageUrl); const base64Data = await imageUrlToBase64(originalImageUrl); - console.log("[抠图] 原图Base64长度:", base64Data.length, "字符"); + logger.debug("rmbg_source_prepared", { base64Length: base64Data.length }); // 调用抠图API const response = await fetch("/api/rmbg", { @@ -917,23 +916,18 @@ export function ImageAnnotator({ if (result.success && result.image_base64) { // 保存处理后的图片URL和Base64(优先使用original_url,确保下载的是原图) - console.log("[抠图] API返回结果:", { + logger.info("rmbg_completed", { hasOriginalUrl: !!result.original_url, hasThumbnailUrl: !!result.thumbnail_url, - originalUrl: result.original_url?.substring(0, 100), - thumbnailUrl: result.thumbnail_url?.substring(0, 100), + base64Length: String(result.image_base64 || "").length, }); if (result.original_url) { - console.log("[抠图] ✅ 使用原图URL:", result.original_url); setProcessedImageUrl(result.original_url); } else if (result.thumbnail_url) { - console.warn( - "[抠图] ⚠️ 未找到original_url,使用thumbnail_url作为备选:", - result.thumbnail_url - ); + logger.warn("rmbg_original_url_missing", { hasThumbnailUrl: true }); setProcessedImageUrl(result.thumbnail_url); } else { - console.warn("[抠图] ⚠️ 未找到图片URL"); + logger.warn("rmbg_image_url_missing"); setProcessedImageUrl(null); } setProcessedImageBase64(result.image_base64); @@ -946,7 +940,7 @@ export function ImageAnnotator({ throw new Error(result.error || result.detail || "抠图处理失败"); } } catch (error) { - console.error("[抠图] 错误:", error); + logger.error("rmbg_failed", error); toast.error( error instanceof Error ? error.message : "抠图失败,请稍后重试" ); @@ -961,8 +955,6 @@ export function ImageAnnotator({ setIsRemovingWatermark(true); try { - console.log("[去水印] 使用原图URL:", originalImageUrl); - const result = await requestLongTaskResult({ endpoint: "/api/watermark-remove", payload: { @@ -971,9 +963,9 @@ export function ImageAnnotator({ }); if (result.success && result.original_url) { - console.log("[去水印] API返回结果:", { - originalUrl: result.original_url?.substring(0, 100), - thumbnailUrl: result.thumbnail_url?.substring(0, 100), + logger.info("watermark_remove_completed", { + hasOriginalUrl: !!result.original_url, + hasThumbnailUrl: !!result.thumbnail_url, }); setProcessedImageUrl(result.original_url); @@ -988,7 +980,7 @@ export function ImageAnnotator({ }; reader.readAsDataURL(blob); } catch { - console.warn("[去水印] 无法获取Base64,仅使用URL"); + logger.warn("watermark_remove_base64_fetch_failed"); } setProcessType("watermark"); @@ -999,7 +991,7 @@ export function ImageAnnotator({ throw new Error(result.error || "去水印处理失败"); } } catch (error) { - console.error("[去水印] 错误:", error); + logger.error("watermark_remove_failed", error); toast.error( error instanceof Error ? error.message : "去水印失败,请稍后重试" ); @@ -1015,9 +1007,8 @@ export function ImageAnnotator({ setIsEnhancing(true); try { // 使用原图URL获取原图数据,而不是canvas的预览图 - console.log("[变清晰] 使用原图URL获取数据:", originalImageUrl); const base64Data = await imageUrlToBase64(originalImageUrl); - console.log("[变清晰] 原图Base64长度:", base64Data.length, "字符"); + logger.debug("enhance_source_prepared", { base64Length: base64Data.length }); const result = await requestLongTaskResult({ endpoint: "/api/enhance", @@ -1032,40 +1023,28 @@ export function ImageAnnotator({ if (result.success && result.image_base64) { // 保存处理后的图片URL和Base64(优先使用original_url,确保下载的是处理后的原图) - console.log("[变清晰] API返回结果:", { + logger.info("enhance_completed", { hasOriginalUrl: !!result.original_url, hasThumbnailUrl: !!result.thumbnail_url, - originalUrl: result.original_url, - thumbnailUrl: result.thumbnail_url, base64Length: result.image_base64?.length || 0, }); // ✅ 验证:检查返回的URL是否是处理后的图片 if (result.original_url) { - console.log("[变清晰] ✅ 使用处理后的原图URL:", result.original_url); - console.log( - "[变清晰] ✅ URL应该包含 'enhance-orig' 标识,确认是处理后的图片" - ); - // 验证URL格式(应该包含enhance-orig) if ( !result.original_url.includes("enhance-orig") && !result.original_url.includes("enhance-orig") ) { - console.warn( - "[变清晰] ⚠️ 警告:URL不包含 'enhance-orig' 标识,可能不是处理后的图片!" - ); + logger.warn("enhance_result_marker_missing"); } setProcessedImageUrl(result.original_url); } else if (result.thumbnail_url) { - console.warn( - "[变清晰] ⚠️ 未找到original_url,使用thumbnail_url作为备选:", - result.thumbnail_url - ); + logger.warn("enhance_original_url_missing", { hasThumbnailUrl: true }); setProcessedImageUrl(result.thumbnail_url); } else { - console.warn("[变清晰] ⚠️ 未找到图片URL"); + logger.warn("enhance_image_url_missing"); setProcessedImageUrl(null); } setProcessedImageBase64(result.image_base64); @@ -1078,7 +1057,7 @@ export function ImageAnnotator({ throw new Error(result.error || result.detail || "变清晰处理失败"); } } catch (error) { - console.error("[变清晰] 错误:", error); + logger.error("enhance_failed", error); toast.error( error instanceof Error ? error.message : "变清晰失败,请稍后重试" ); @@ -1359,7 +1338,7 @@ export function ImageAnnotator({ const finalResultUrl = result.original_url || dataUrl; if (result.original_url) { - console.log("[尺寸调整] 原图URL:", result.original_url); + logger.info("resize_completed", { hasOriginalUrl: true }); setProcessedImageUrl(result.original_url); } else { setProcessedImageUrl(null); @@ -1372,7 +1351,7 @@ export function ImageAnnotator({ throw new Error(result.error || "尺寸调整处理失败"); } } catch (error) { - console.error("[尺寸调整] 错误:", error); + logger.error("resize_failed", error); toast.error( error instanceof Error ? error.message : "尺寸调整失败,请稍后重试" ); @@ -1388,8 +1367,6 @@ export function ImageAnnotator({ setIsExtending(true); try { // 直接使用原图URL,而不是转换为base64 - console.log("[图像拓展] 使用原图URL:", originalImageUrl); - // 调用图像拓展API(直接发送URL) const response = await fetch("/api/extend", { method: "POST", @@ -1413,15 +1390,17 @@ export function ImageAnnotator({ if (result.success) { // 保存处理后的图片URL和Base64(优先使用original_url,确保下载的是原图) if (result.original_url) { - console.log("[图像拓展] 原图URL:", result.original_url); + logger.info("extend_completed", { + hasOriginalUrl: true, + hasThumbnailUrl: !!result.thumbnail_url, + hasBase64: !!result.base64, + }); setProcessedImageUrl(result.original_url); } else if (result.thumbnail_url) { - console.warn( - "[图像拓展] ⚠️ 未找到original_url,使用thumbnail_url作为备选" - ); + logger.warn("extend_original_url_missing", { hasThumbnailUrl: true }); setProcessedImageUrl(result.thumbnail_url); } else { - console.warn("[图像拓展] ⚠️ 未找到图片URL"); + logger.warn("extend_image_url_missing"); setProcessedImageUrl(null); } // 优先使用返回的base64,如果没有则使用thumbnail_url或original_url @@ -1435,7 +1414,7 @@ export function ImageAnnotator({ throw new Error(result.error || result.detail || "图像拓展处理失败"); } } catch (error) { - console.error("[图像拓展] 错误:", error); + logger.error("extend_failed", error); toast.error( error instanceof Error ? error.message : "图像拓展失败,请稍后重试" ); @@ -1507,9 +1486,7 @@ export function ImageAnnotator({ } // ✅ 验证:确认下载的是处理后的图片URL - console.log("[下载] 📥 开始下载,processType:", processType); - console.log("[下载] 📥 processedImageUrl:", processedImageUrl); - console.log("[下载] 📥 这个URL应该是处理后的图片,不是原图!"); + logger.debug("download_started", { processType }); // 确保使用的是处理后的原图URL(检查URL中是否包含thumb标识) let downloadUrl = processedImageUrl; @@ -1526,7 +1503,7 @@ export function ImageAnnotator({ .replace(/-thumb\./g, "-orig.") .replace(/\/thumb\//g, "/orig/") .replace(/thumbnail/g, "original"); - console.warn("[下载] ⚠️ 检测到缩略图URL,已转换为原图URL:", downloadUrl); + logger.warn("download_thumbnail_url_rewritten", { processType }); } // 如果转换后的URL仍然包含thumb标识,尝试更激进的转换 @@ -1538,18 +1515,20 @@ export function ImageAnnotator({ if (pathname.includes("thumb")) { const newPathname = pathname.replace(/thumb/g, "orig"); downloadUrl = downloadUrl.replace(pathname, newPathname); - console.warn("[下载] ⚠️ 二次转换原图URL:", downloadUrl); + logger.warn("download_thumbnail_url_rewritten_again", { processType }); } } catch (urlError) { // 如果URL解析失败,尝试简单的字符串替换 downloadUrl = downloadUrl.replace(/thumb/g, "orig"); - console.warn("[下载] ⚠️ 使用简单替换转换原图URL:", downloadUrl); + logger.warn("download_thumbnail_url_string_rewrite", { + processType, + reason: urlError instanceof Error ? urlError.message : "unknown", + }); } } // ✅ 验证:确认下载URL是正确的 - console.log("[下载] ✅ 最终下载URL:", downloadUrl); - console.log("[下载] ✅ 这个URL应该指向处理后的图片(不是原图)"); + logger.debug("download_url_resolved", { processType }); // 如果是变清晰,验证URL应该包含enhance相关标识 if ( @@ -1557,9 +1536,7 @@ export function ImageAnnotator({ !downloadUrl.includes("enhance") && !downloadUrl.includes("1767358346590") ) { - console.warn( - "[下载] ⚠️ 警告:变清晰下载URL不包含 'enhance' 标识,可能下载了错误的图片!" - ); + logger.warn("download_enhance_marker_missing"); } // 从原图URL下载(原图已上传到S3) @@ -1570,7 +1547,7 @@ export function ImageAnnotator({ ); } const blob = await response.blob(); - console.log("[下载] 下载的图片大小:", blob.size, "bytes"); + logger.info("download_blob_ready", { bytes: blob.size, processType }); const url = window.URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; @@ -1582,7 +1559,7 @@ export function ImageAnnotator({ toast.success("原图下载成功"); } catch (error) { - console.error("[下载] 错误:", error); + logger.error("download_failed", error); toast.error( `下载失败: ${error instanceof Error ? error.message : "请稍后重试"}` ); @@ -1615,7 +1592,7 @@ export function ImageAnnotator({ } return uploadResult.url; } catch (error) { - console.error("上传图片失败:", error); + logger.error("server_upload_failed", error); throw error; } }; diff --git a/scripts/check-log-hygiene.mjs b/scripts/check-log-hygiene.mjs index e1a524233..3d7a2cac2 100644 --- a/scripts/check-log-hygiene.mjs +++ b/scripts/check-log-hygiene.mjs @@ -10,6 +10,12 @@ const files = [ "lib/ai/providers/gemini-image.ts", "lib/ai/providers/volcano-vision.ts", "lib/ai/providers/qianwen-video.ts", + "services/canvas/index.ts", + "components/image-annotator.tsx", + "app/api/canvas/auto-title/route.ts", + "app/api/canvas/text-detect/route.ts", + "app/api/canvas/detail-plan/route.ts", + "app/api/scene-fusion/route.ts", ]; const consolePattern = /\bconsole\.(log|warn|error|info|debug)\b/g; diff --git a/services/canvas/index.ts b/services/canvas/index.ts index e91c97f2b..3a8bd52b3 100644 --- a/services/canvas/index.ts +++ b/services/canvas/index.ts @@ -14,7 +14,7 @@ import type { EnhanceProOutputFormat, EnhanceProSubjectDetection, } from '@/lib/enhance-pro/config'; -import { createLogger } from '@/lib/logger'; +import { createLogger } from '@/lib/observability/logger'; const log = createLogger("Canvas"); @@ -267,7 +267,7 @@ export const imageGenerationService = { } }; } catch (error) { - console.error('[Canvas] 图片上传失败:', error); + logCatch(error, "image_upload"); return { success: false, error: '图片上传失败' }; } }, @@ -374,7 +374,7 @@ export const imageGenerationService = { } return { success: false, error: data.error || '局部编辑提交失败' }; } catch (error) { - console.error('[Canvas] 局部编辑提交失败:', error); + logCatch(error, "local_edit_submit"); return { success: false, error: '局部编辑提交失败' }; } }, @@ -403,7 +403,7 @@ export const imageGenerationService = { } return { success: false, error: data.error || '查询生图任务失败' }; } catch (error) { - console.error('[Canvas] 查询生图任务失败:', error); + logCatch(error, "image_task_fetch", { taskId }); return { success: false, error: '查询生图任务失败' }; } }, @@ -422,7 +422,7 @@ export const imageGenerationService = { } return { success: false, error: data.error || '抠图失败' }; } catch (error) { - console.error('[Canvas] 抠图失败:', error); + logCatch(error, "remove_background"); return { success: false, error: '抠图失败' }; } }, @@ -458,7 +458,7 @@ export const imageGenerationService = { } return { success: false, error: data.error || '框选抠图失败' }; } catch (error) { - console.error('[Canvas] 框选抠图失败:', error); + logCatch(error, "box_cutout"); return { success: false, error: '框选抠图失败' }; } }, @@ -477,7 +477,7 @@ export const imageGenerationService = { } return { success: false, error: data.error || '去水印失败' }; } catch (error) { - console.error('[Canvas] 去水印失败:', error); + logCatch(error, "remove_watermark"); return { success: false, error: '去水印失败' }; } }, @@ -504,7 +504,7 @@ export const imageGenerationService = { } return { success: false, error: data.error || '图片增强失败' }; } catch (error) { - console.error('[Canvas] 图片增强失败:', error); + logCatch(error, "enhance_image"); return { success: false, error: '图片增强失败' }; } }, @@ -559,7 +559,7 @@ export const imageGenerationService = { }, }; } catch (error) { - console.error('[Canvas] 图像增强 Pro 失败:', error); + logCatch(error, "enhance_image_pro"); if (error instanceof Error && /failed to fetch|network/i.test(error.message)) { return { success: false, @@ -612,7 +612,7 @@ export const imageGenerationService = { } return { success: false, error: data.error || data.detail || '图片扩展失败' }; } catch (error) { - console.error('[Canvas] 图片扩展失败:', error); + logCatch(error, "extend_image"); return { success: false, error: '图片扩展失败' }; } }, @@ -658,7 +658,7 @@ export const imageGenerationService = { } return { success: false, error: data.error || '图层拆分失败' }; } catch (error) { - console.error('[Canvas] 图层拆分失败:', error); + logCatch(error, "split_image_layers"); return { success: false, error: '图层拆分失败' }; } }, @@ -714,7 +714,7 @@ export const imageGenerationService = { } return { success: false, error: data.error || '尺寸调整失败' }; } catch (error) { - console.error('[Canvas] 尺寸调整失败:', error); + logCatch(error, "resize_image", { width, height }); return { success: false, error: '尺寸调整失败' }; } }, @@ -796,7 +796,7 @@ export const imageGenerationService = { const data = await response.json(); return data; } catch (error) { - console.error('[Canvas] 文字识别失败:', error); + logCatch(error, "detect_canvas_text"); return { success: false, error: '文字识别失败' }; } }, @@ -826,7 +826,7 @@ export const imageGenerationService = { const data = await response.json(); return data; } catch (error) { - console.error('[Canvas] 文案建议生成失败:', error); + logCatch(error, "text_suggestions"); return { success: false, error: '文案建议生成失败' }; } }, @@ -845,7 +845,7 @@ export const imageGenerationService = { const data = await response.json(); return data; } catch (error) { - console.error('[Canvas] 文字替换失败:', error); + logCatch(error, "edit_canvas_text", { itemCount: params.items.length }); return { success: false, error: '文字替换失败' }; } },