fix: resolve 5 Sentry production errors (668 total events)

- Filter known client-error noise (rate limit, empty body, unsupported
  media type, content-length mismatch, premature close) from Sentry
  via beforeSend to stop 644 events of non-actionable noise
- Sanitize x-output-filename header to prevent TypeError on non-ASCII
  filenames in optimize-for-web preview (23 events)
- Handle EPIPE on Python dispatcher stdin write with graceful fallback
  to per-request spawning instead of crashing (NODE-W)
- Map EACCES on storage directory/file write to proper 503 status
  instead of generic 500 (NODE-P, 3 events)
This commit is contained in:
SnapOtter
2026-05-16 12:22:24 +08:00
parent 2c45a3a9e8
commit 212ef653b5
4 changed files with 49 additions and 5 deletions
+10
View File
@@ -39,6 +39,16 @@ export async function initAnalytics(): Promise<void> {
} }
if (event.exception?.values) { if (event.exception?.values) {
for (const ex of event.exception.values) { for (const ex of event.exception.values) {
if (
ex.value &&
(ex.value.includes("Rate limit exceeded") ||
ex.value.includes("Body cannot be empty") ||
ex.value.includes("Unsupported Media Type") ||
ex.value.includes("Request body size did not match") ||
ex.value.includes("Premature close"))
) {
return null;
}
if (ex.value) { if (ex.value) {
ex.value = ex.value ex.value = ex.value
.replace(FILE_EXT_PATTERN, ".[REDACTED]") .replace(FILE_EXT_PATTERN, ".[REDACTED]")
+22
View File
@@ -56,7 +56,18 @@ let storageReady = false;
export async function ensureStorageDir(): Promise<void> { export async function ensureStorageDir(): Promise<void> {
if (storageReady) return; if (storageReady) return;
try {
await mkdir(env.FILES_STORAGE_PATH, { recursive: true }); await mkdir(env.FILES_STORAGE_PATH, { recursive: true });
} catch (e) {
if (e instanceof Error && (e as NodeJS.ErrnoException).code === "EACCES") {
const err = new Error("Storage directory is not writable") as Error & {
statusCode: number;
};
err.statusCode = 503;
throw err;
}
throw e;
}
storageReady = true; storageReady = true;
} }
@@ -70,7 +81,18 @@ export async function saveFile(buffer: Buffer, originalName: string): Promise<st
ext = ".bin"; ext = ".bin";
} }
const storedName = `${randomUUID()}${ext}`; const storedName = `${randomUUID()}${ext}`;
try {
await writeFile(join(env.FILES_STORAGE_PATH, storedName), buffer); await writeFile(join(env.FILES_STORAGE_PATH, storedName), buffer);
} catch (e) {
if (e instanceof Error && (e as NodeJS.ErrnoException).code === "EACCES") {
const err = new Error("Storage directory is not writable") as Error & {
statusCode: number;
};
err.statusCode = 503;
throw err;
}
throw e;
}
return storedName; return storedName;
} }
@@ -159,7 +159,8 @@ export function registerOptimizeForWeb(app: FastifyInstance) {
reply.header("Content-Type", result.contentType); reply.header("Content-Type", result.contentType);
reply.header("X-Original-Size", String(fileBuffer.length)); reply.header("X-Original-Size", String(fileBuffer.length));
reply.header("X-Processed-Size", String(result.buffer.length)); reply.header("X-Processed-Size", String(result.buffer.length));
reply.header("X-Output-Filename", encodeURIComponent(result.filename)); const safeFilename = encodeURIComponent(result.filename).replace(/[^ -~]/g, "");
reply.header("X-Output-Filename", safeFilename);
return reply.send(result.buffer); return reply.send(result.buffer);
} catch (err) { } catch (err) {
const message = err instanceof Error ? err.message : "Preview processing failed"; const message = err instanceof Error ? err.message : "Preview processing failed";
+12 -1
View File
@@ -153,6 +153,8 @@ function startDispatcher(): ChildProcess | null {
env: buildMinimalEnv(), env: buildMinimalEnv(),
}); });
child.stdin?.on("error", () => {});
let stderrBuffer = ""; let stderrBuffer = "";
child.stderr?.on("data", (chunk: Buffer) => { child.stderr?.on("data", (chunk: Buffer) => {
@@ -328,7 +330,13 @@ function dispatcherRun(
}); });
const request = JSON.stringify({ id, script: scriptName.replace(".py", ""), args }); const request = JSON.stringify({ id, script: scriptName.replace(".py", ""), args });
try {
proc.stdin!.write(request + "\n"); proc.stdin!.write(request + "\n");
} catch {
pendingRequests.delete(id);
clearTimeout(timer);
rejectPromise(new Error("Python dispatcher stdin closed unexpectedly"));
}
}); });
} }
@@ -537,7 +545,10 @@ export function runPythonWithProgress(
const dispatcherPromise = dispatcherRun(scriptName, args, options); const dispatcherPromise = dispatcherRun(scriptName, args, options);
if (dispatcherPromise) { if (dispatcherPromise) {
return dispatcherPromise.catch((err: Error) => { return dispatcherPromise.catch((err: Error) => {
if (err.message === "Python dispatcher exited unexpectedly") { if (
err.message === "Python dispatcher exited unexpectedly" ||
err.message === "Python dispatcher stdin closed unexpectedly"
) {
console.warn( console.warn(
`[bridge] Dispatcher crashed during ${scriptName}, retrying with per-request process`, `[bridge] Dispatcher crashed during ${scriptName}, retrying with per-request process`,
); );