Log export lifecycle (start/ok/fail/lock) with stack and timing

This commit is contained in:
Misaka_Company
2026-06-24 13:40:27 +08:00
parent 3371a8af2a
commit bc0e95157c

View File

@@ -1,6 +1,7 @@
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import sql from "mssql"; import sql from "mssql";
import ExcelJS from "exceljs"; import ExcelJS from "exceljs";
import { exportLogger, formatError } from "../../../server/logger";
const dbConfig = { const dbConfig = {
server: process.env.DB_SERVER!, server: process.env.DB_SERVER!,
@@ -55,7 +56,7 @@ const EXPORT_LOCK_TIMEOUT_MS = 90_000; // safety: reclaim a stuck lock after 90s
function acquireExportLock(): boolean { function acquireExportLock(): boolean {
if (exportLocked && Date.now() - exportLockedAt > EXPORT_LOCK_TIMEOUT_MS) { if (exportLocked && Date.now() - exportLockedAt > EXPORT_LOCK_TIMEOUT_MS) {
console.warn("Export lock reclaimed after timeout"); exportLogger.warn("export lock reclaimed after timeout");
exportLocked = false; exportLocked = false;
} }
if (exportLocked) return false; if (exportLocked) return false;
@@ -70,11 +71,14 @@ function releaseExportLock() {
export async function GET() { export async function GET() {
if (!acquireExportLock()) { if (!acquireExportLock()) {
exportLogger.warn("export skipped: another export in progress (429)");
return NextResponse.json( return NextResponse.json(
{ error: "正在导出,请稍候" }, { error: "正在导出,请稍候" },
{ status: 429, headers: { "Retry-After": "3" } } { status: 429, headers: { "Retry-After": "3" } }
); );
} }
const t0 = Date.now();
exportLogger.info("export started");
let pool: sql.ConnectionPool | undefined; let pool: sql.ConnectionPool | undefined;
try { try {
pool = await sql.connect(dbConfig); pool = await sql.connect(dbConfig);
@@ -84,6 +88,7 @@ export async function GET() {
const recordset = result.recordset; const recordset = result.recordset;
if (!recordset || recordset.length === 0) { if (!recordset || recordset.length === 0) {
exportLogger.warn("export empty: no data");
return NextResponse.json({ error: "没有数据可导出" }, { status: 404 }); return NextResponse.json({ error: "没有数据可导出" }, { status: 404 });
} }
@@ -151,6 +156,12 @@ export async function GET() {
// Stream to buffer // Stream to buffer
const buffer = await workbook.xlsx.writeBuffer(); const buffer = await workbook.xlsx.writeBuffer();
const bytes =
typeof buffer === "object" && buffer && "byteLength" in buffer
? (buffer as { byteLength: number }).byteLength
: 0;
const dur = Date.now() - t0;
exportLogger.info(`export ok · rows=${recordset.length} · bytes=${bytes} · dur=${dur}ms`);
return new NextResponse(buffer, { return new NextResponse(buffer, {
status: 200, status: 200,
@@ -162,8 +173,9 @@ export async function GET() {
}, },
}); });
} catch (err) { } catch (err) {
const dur = Date.now() - t0;
const message = err instanceof Error ? err.message : "Unknown error"; const message = err instanceof Error ? err.message : "Unknown error";
console.error("Excel export error:", message); exportLogger.error(`export failed · dur=${dur}ms · ${formatError(err)}`);
return NextResponse.json({ error: message }, { status: 500 }); return NextResponse.json({ error: message }, { status: 500 });
} finally { } finally {
if (pool) { if (pool) {