mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: error-only Sentry telemetry, storm-proof capture, and crash fixes (#476)
Removes Sentry tracing entirely (BullMQ idle polling burned 4.8M transactions in 2 days at the baked 0.1 rate), decouples PostHog sampling, and replaces the type-only error scrub with a vetted-field sanitizer plus SafeError/ToolInputError contracts. One classified capture path with per-signature throttles and a per-process ceiling makes storms impossible (NODE-1E was 4,541 events from one 30s loop). Browser errors move to a dedicated web Sentry project with their own source maps. Adds the SNAPOTTER_TELEMETRY runtime kill switch and silences test fleets. Crash fixes: remote 204/304 SSRF process kill (NODE-20), conversion-preset boot crash loop (NODE-21), Redis version preflight + unhandled subscribe rejection (NODE-1T), Sign PDF on plain-http origins (NODE-1K/1M), wavesurfer/pdf.js teardown rejections (NODE-1P/1N), bundle-import ZlibError to 400 (NODE-1Z), chart-maker input errors declassified (NODE-1H/1J), asset requests skip the session DB lookup (NODE-1D).
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import { markToolInputError } from "@snapotter/shared";
|
||||
import { resolveFfmpeg } from "./binaries.js";
|
||||
import { type FfmpegProgress, parseProgressBlock } from "./progress.js";
|
||||
|
||||
@@ -10,6 +11,23 @@ export interface RunFfmpegOptions {
|
||||
|
||||
const STDERR_RING_MAX = 16 * 1024;
|
||||
|
||||
// stderr shapes that mean "the input media is unusable", not an ffmpeg bug.
|
||||
// Marked errors are classified expected upstream and never reach Sentry.
|
||||
const INPUT_ERROR_PATTERNS = [
|
||||
/received no packets/i,
|
||||
/invalid data found when processing input/i,
|
||||
/could not find codec parameters/i,
|
||||
/moov atom not found/i,
|
||||
];
|
||||
|
||||
/**
|
||||
* Applies the isToolInputError marker (see @snapotter/shared tool-errors.ts)
|
||||
* when the captured stderr matches a known input-failure shape.
|
||||
*/
|
||||
export function markIfInputError<E extends Error>(err: E, stderr: string): E {
|
||||
return INPUT_ERROR_PATTERNS.some((re) => re.test(stderr)) ? markToolInputError(err) : err;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs ffmpeg with `-progress pipe:1` appended, parsing progress blocks from
|
||||
* stdout. Rejects with the tail of stderr on non-zero exit, timeout or abort.
|
||||
@@ -76,7 +94,10 @@ export async function runFfmpeg(args: string[], opts: RunFfmpegOptions = {}): Pr
|
||||
settled = true;
|
||||
cleanup();
|
||||
if (code === 0) resolvePromise(stderrTail);
|
||||
else reject(new Error(`ffmpeg exited ${code ?? signal}: ${stderrTail.slice(-2000)}`));
|
||||
else {
|
||||
const err = new Error(`ffmpeg exited ${code ?? signal}: ${stderrTail.slice(-2000)}`);
|
||||
reject(markIfInputError(err, stderrTail));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import { resolveFfprobe } from "./binaries.js";
|
||||
import { markIfInputError } from "./ffmpeg.js";
|
||||
|
||||
export interface MediaStreamInfo {
|
||||
type: "video" | "audio" | "other";
|
||||
@@ -68,7 +69,10 @@ export async function probeMedia(filePath: string, opts: ProbeOptions = {}): Pro
|
||||
settled = true;
|
||||
clearTimeout(timer);
|
||||
if (code === 0) resolvePromise(out);
|
||||
else reject(new Error(`ffprobe exited ${code ?? signal}: ${err.slice(-1000)}`));
|
||||
else {
|
||||
const probeErr = new Error(`ffprobe exited ${code ?? signal}: ${err.slice(-1000)}`);
|
||||
reject(markIfInputError(probeErr, err));
|
||||
}
|
||||
});
|
||||
});
|
||||
const parsed = JSON.parse(stdout) as {
|
||||
|
||||
Reference in New Issue
Block a user