chore: govern layer split logging

This commit is contained in:
Your Name 2026-05-27 13:12:05 +08:00
parent 8e91c00c74
commit 37ed4b83b2
3 changed files with 54 additions and 5 deletions

View File

@ -1,6 +1,9 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/app/(auth)/auth";
import { executeLayerSplitJob, LayerSplitServiceError } from "@/lib/ai/layer-split/service";
import { createLogger } from "@/lib/observability/logger";
const logger = createLogger("layer-split-api");
export const runtime = "nodejs";
export const maxDuration = 300;
@ -51,7 +54,7 @@ export async function POST(request: NextRequest) {
);
}
console.error("[LayerSplit API] 处理错误:", error);
logger.error("request_failed", error);
return NextResponse.json(
{ error: "服务器内部错误", detail: error instanceof Error ? error.message : "未知错误" },
{ status: 500 }

View File

@ -1,12 +1,20 @@
import "server-only";
import JSZip from "jszip";
import { createMediaAssetRelation, getMediaAssetsByUserAndUrls, getSystemSetting, upsertMediaAsset } from "@/lib/db/queries";
import {
createMediaAssetRelation,
getMediaAssetsByUserAndUrls,
getSystemSetting,
upsertMediaAsset,
} from "@/lib/db/queries";
import { db } from "@/lib/db";
import { generatedImage } from "@/lib/db/schema";
import { createLogger } from "@/lib/observability/logger";
import { ensurePublicImageUrl } from "@/lib/realtime/image-input";
import { uploadFile } from "@/lib/storage/service";
const logger = createLogger("layer-split-service");
const LAYER_SPLIT_CONFIG_KEY = "layer_split_config";
const DEFAULT_REPLICATE_BASE_URL = "https://api.replicate.com/v1";
const DEFAULT_LAYER_SPLIT_MODEL = "qwen/qwen-image-layered";
@ -443,7 +451,7 @@ async function createThumbnail(buffer: Buffer, mimeType: string): Promise<Buffer
}
return await pipeline.png({ compressionLevel: 8 }).toBuffer();
} catch (error) {
console.error("[LayerSplit] Failed to create thumbnail:", error);
logger.warn("thumbnail_creation_failed", error);
return buffer;
}
}
@ -497,7 +505,7 @@ async function detectVisibleBounds(buffer: Buffer) {
coverage: visiblePixels / (width * height),
};
} catch (error) {
console.warn("[LayerSplit] Failed to detect visible bounds:", error);
logger.warn("visible_bounds_detection_failed", error);
return null;
}
}
@ -524,6 +532,15 @@ async function uploadBufferPair(buffer: Buffer, mimeType: string, prefix: string
uploadFile(origFilename, originalArrayBuffer),
]);
logger.info("layer_upload_completed", {
mimeType,
originalBytes: buffer.length,
originalUrl: originalResult.url,
prefix,
thumbnailBytes: thumbnailBuffer.length,
thumbnailUrl: thumbnailResult.url,
});
return {
thumbnail: thumbnailResult.url,
original: originalResult.url,
@ -573,6 +590,11 @@ async function runReplicateLayerSplit(params: {
});
lastPayload = payload;
logger.debug("replicate_prediction_attempt_completed", {
attempt: index + 1,
hasPayload: payload != null,
status: response.status,
});
if (
response.ok &&
@ -601,6 +623,10 @@ async function runReplicateLayerSplit(params: {
}
if (index < candidateInputs.length - 1 && shouldRetryWithAlternativeInput(errorMessage)) {
logger.warn("replicate_retrying_with_alternative_input", {
attempt: index + 1,
reasonLength: errorMessage.length,
});
continue;
}
}
@ -615,6 +641,13 @@ async function runReplicateLayerSplit(params: {
export async function executeLayerSplitJob(params: LayerSplitParams): Promise<ExecuteLayerSplitJobResult> {
const startedAt = Date.now();
const config = await getValidatedLayerSplitConfig();
logger.info("job_started", {
hasChatId: Boolean(params.chatId),
hasMessageId: Boolean(params.messageId),
hasPrompt: Boolean(params.prompt),
hasSourceNodeId: Boolean(params.sourceNodeId),
skipGallerySave: Boolean(params.skipGallerySave),
});
const normalizedImageUrl = await ensurePublicImageUrl({
imageUrl: params.imageUrl,
prefix: "layer-split-input",
@ -630,6 +663,10 @@ export async function executeLayerSplitJob(params: LayerSplitParams): Promise<Ex
const layerBuffers = await resolveLayerArtifacts(payload);
const totalLayers = layerBuffers.length;
logger.info("layer_artifacts_resolved", {
model: config.replicateModel,
totalLayers,
});
const sourceAssets = await getMediaAssetsByUserAndUrls({
userId: params.userId,
urls:
@ -724,10 +761,17 @@ export async function executeLayerSplitJob(params: LayerSplitParams): Promise<Ex
});
}
const processingTime = (Date.now() - startedAt) / 1000;
logger.info("job_completed", {
layerCount: results.length,
processingSeconds: Number(processingTime.toFixed(2)),
savedToGallery: !params.skipGallerySave,
});
return {
layers: results,
provider: "replicate",
model: config.replicateModel,
processingTime: (Date.now() - startedAt) / 1000,
processingTime,
};
}

View File

@ -25,6 +25,7 @@ const files = [
"lib/ai/providers/volcano-vision.ts",
"lib/ai/providers/qianwen-video.ts",
"lib/ai/rmbg/service.ts",
"lib/ai/layer-split/service.ts",
"lib/ai/dynamic-provider.ts",
"services/canvas/index.ts",
"components/image-annotator.tsx",
@ -33,6 +34,7 @@ const files = [
"app/api/canvas/detail-plan/route.ts",
"app/api/scene-fusion/route.ts",
"app/api/angle-edit/route.ts",
"app/api/layer-split/route.ts",
"app/api/pick-object/route.ts",
"app/api/canvas/world/upload/route.ts",
"app/api/canvas/3d/tasks/route.ts",