Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | 43x 43x 43x 43x 22x 22x 7x 15x 11x 11x 11x 11x 4x 43x 13x 13x 13x 12x 12x 1x 1x 11x 11x 1x 1x 10x 10x 22x 17x 10x 6x 6x 4x 1x 1x | import url from "node:url";
import { ApiError } from "shared/types/api/errors";
import { logger } from "server/utils/logger";
import {
flattenErrorMessageChain,
retryWithDelays,
sentryRetryDelaysMs,
} from "server/utils/sentryRetry";
const sentryHost = "sentry.io";
const knownProjectIds = new Set(["/6579203", "/6578391", "/6579491"]);
const fetchTimeoutMs = 1000 * 15;
interface ResendSentryError extends ApiError {
errorId: "unknown";
}
type ForwardAttempt =
| { delivered: true }
| { delivered: false; retryable: boolean; error: Error };
const attemptForward = async (
sentryUrl: string,
envelope: Buffer,
): Promise<ForwardAttempt> => {
let response: Response;
try {
response = await fetch(sentryUrl, {
method: "POST",
headers: { "Content-Type": "application/x-sentry-envelope" },
body: new Uint8Array(envelope),
signal: AbortSignal.timeout(fetchTimeoutMs),
});
} catch (fetchError) {
return {
delivered: false,
retryable: true,
error: new Error(
`Sentry tunnel: upstream request failed: ${flattenErrorMessageChain(fetchError)}`,
),
};
}
if (!response.ok) {
// Sentry explains rejections in the x-sentry-error header and/or body
const sentryError = response.headers.get("x-sentry-error");
const body = (await response.text()).slice(0, 500);
const details = [sentryError, body].filter(Boolean).join(" / ");
return {
delivered: false,
// Retrying a 4xx would resend an envelope Sentry already rejected
retryable: response.status >= 500,
error: new Error(
`Sentry tunnel: upstream responded with ${response.status}${details ? `: ${details}` : ""}`,
),
};
}
return { delivered: true };
};
export const resendSentryRequest = async (
envelope: Buffer,
): Promise<null | ResendSentryError> => {
try {
const piece = envelope.subarray(0, envelope.indexOf("\n"));
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const header = JSON.parse(piece.toString());
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const { hostname, pathname } = new url.URL(header.dsn as string);
if (!hostname.endsWith(`.${sentryHost}`)) {
logger.error(new Error(`invalid host: ${hostname}`));
return {
message: "Sentry tunnel: Invalid host",
status: "error",
errorId: "unknown",
};
}
const projectId = pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
if (!knownProjectIds.has(projectId)) {
logger.error(new Error(`invalid project id: ${projectId}`));
return {
message: "Sentry tunnel: Invalid project",
status: "error",
errorId: "unknown",
};
}
const sentryUrl = `https://${hostname}/api${projectId}/envelope/`;
// Upstream failures are usually transient load shedding, so retry with
// increasing delays before logging the failure
const attempt = await retryWithDelays(
sentryRetryDelaysMs,
async () => attemptForward(sentryUrl, envelope),
(result) => !result.delivered && result.retryable,
);
if (!attempt.delivered) {
logger.error(attempt.error);
return {
message: "Sentry tunnel: Upstream error",
status: "error",
errorId: "unknown",
};
}
return null;
} catch (error) {
logger.error(new Error("Sentry tunnel error", { cause: error }));
return {
message: "Sentry tunnel: Unknown error",
status: "error",
errorId: "unknown",
};
}
};
|