mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 03:23:12 +00:00
151 lines
4.4 KiB
TypeScript
151 lines
4.4 KiB
TypeScript
import type { Breadcrumb, ErrorEvent, SpanJSON, TransactionEvent } from "@sentry/core";
|
|
|
|
import { normalizeHttpRoute } from "@/observability/http-transaction";
|
|
|
|
function sanitizeUrl(value: string): string {
|
|
try {
|
|
const parsed = new URL(value, window.location.origin);
|
|
return `${parsed.origin}${normalizeHttpRoute(parsed.pathname)}`;
|
|
} catch {
|
|
return normalizeHttpRoute(value);
|
|
}
|
|
}
|
|
|
|
function keepOpaqueUserId(event: ErrorEvent | TransactionEvent): void {
|
|
event.user = event.user?.id == null ? undefined : { id: String(event.user.id) };
|
|
}
|
|
|
|
function scrubRequest(event: ErrorEvent | TransactionEvent): void {
|
|
if (!event.request) {
|
|
return;
|
|
}
|
|
|
|
event.request = {
|
|
method: event.request.method,
|
|
url: typeof event.request.url === "string" ? sanitizeUrl(event.request.url) : undefined,
|
|
};
|
|
}
|
|
|
|
const SAFE_SPAN_DATA_KEYS = new Set([
|
|
"actor.type",
|
|
"code.function",
|
|
"db.operation.name",
|
|
"db.system",
|
|
"http.method",
|
|
"http.request.method",
|
|
"http.response.status_code",
|
|
"http.route",
|
|
"http.status_code",
|
|
"operation.type",
|
|
"sentry.op",
|
|
"sentry.origin",
|
|
"server.address",
|
|
]);
|
|
|
|
function safeSpanData(data: SpanJSON["data"] | undefined): SpanJSON["data"] {
|
|
return Object.fromEntries(
|
|
Object.entries(data ?? {}).filter(
|
|
([key, value]) =>
|
|
SAFE_SPAN_DATA_KEYS.has(key) && ["string", "number", "boolean"].includes(typeof value),
|
|
),
|
|
);
|
|
}
|
|
|
|
function valueAsString(value: unknown): string | undefined {
|
|
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
}
|
|
|
|
function sanitizeHttpDescription(description: string | undefined): string | undefined {
|
|
if (!description) {
|
|
return undefined;
|
|
}
|
|
|
|
const match = /^(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+(.+)$/i.exec(description);
|
|
return match ? `${match[1]!.toUpperCase()} ${sanitizeUrl(match[2]!)}` : undefined;
|
|
}
|
|
|
|
function scrubTransactionSpans(event: TransactionEvent): void {
|
|
event.spans = event.spans?.map((span) => ({
|
|
...span,
|
|
data: safeSpanData(span.data),
|
|
description: span.op?.includes("http") ? sanitizeHttpDescription(span.description) : undefined,
|
|
}));
|
|
|
|
const trace = event.contexts?.trace;
|
|
if (trace?.data) {
|
|
trace.data = safeSpanData(trace.data);
|
|
}
|
|
}
|
|
|
|
function setStringTag(
|
|
tags: NonNullable<TransactionEvent["tags"]>,
|
|
key: string,
|
|
value: unknown,
|
|
): void {
|
|
const stringValue = valueAsString(value);
|
|
if (stringValue) {
|
|
tags[key] = stringValue;
|
|
}
|
|
}
|
|
|
|
function transactionFunction(event: ErrorEvent | TransactionEvent): string | undefined {
|
|
const traceFunction = valueAsString(event.contexts?.trace?.data?.["code.function"]);
|
|
return traceFunction ?? (event.type === "transaction" ? event.transaction : undefined);
|
|
}
|
|
|
|
function actorType(event: ErrorEvent | TransactionEvent): string {
|
|
return (
|
|
valueAsString(event.tags?.["actor.type"]) ??
|
|
valueAsString(event.contexts?.trace?.data?.["actor.type"]) ??
|
|
(event.user?.id == null ? "anonymous" : "authenticated")
|
|
);
|
|
}
|
|
|
|
function attachTraceIdentifiers(event: ErrorEvent | TransactionEvent): void {
|
|
const trace = event.contexts?.trace;
|
|
const tags = { ...event.tags };
|
|
|
|
setStringTag(tags, "trace_id", trace?.trace_id);
|
|
setStringTag(tags, "transaction_id", trace?.span_id);
|
|
setStringTag(tags, "operation.type", valueAsString(trace?.data?.["operation.type"]) ?? trace?.op);
|
|
setStringTag(tags, "code.function", transactionFunction(event));
|
|
tags["actor.type"] = actorType(event);
|
|
event.tags = tags;
|
|
}
|
|
|
|
export function scrubErrorEvent(event: ErrorEvent): ErrorEvent {
|
|
keepOpaqueUserId(event);
|
|
scrubRequest(event);
|
|
attachTraceIdentifiers(event);
|
|
return event;
|
|
}
|
|
|
|
export function scrubTransactionEvent(event: TransactionEvent): TransactionEvent {
|
|
keepOpaqueUserId(event);
|
|
scrubRequest(event);
|
|
attachTraceIdentifiers(event);
|
|
scrubTransactionSpans(event);
|
|
return event;
|
|
}
|
|
|
|
export function scrubBreadcrumb(breadcrumb: Breadcrumb): Breadcrumb {
|
|
const safe: Breadcrumb = {
|
|
timestamp: breadcrumb.timestamp,
|
|
type: breadcrumb.type,
|
|
category: breadcrumb.category,
|
|
level: breadcrumb.level,
|
|
};
|
|
|
|
if (breadcrumb.category === "fetch" || breadcrumb.category === "xhr") {
|
|
const method = breadcrumb.data?.method;
|
|
const statusCode = breadcrumb.data?.status_code;
|
|
const url = breadcrumb.data?.url;
|
|
safe.data = {
|
|
...(typeof method === "string" ? { method } : {}),
|
|
...(typeof statusCode === "number" ? { status_code: statusCode } : {}),
|
|
...(typeof url === "string" ? { url: sanitizeUrl(url) } : {}),
|
|
};
|
|
}
|
|
|
|
return safe;
|
|
}
|