mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 02:29:55 +02:00
refactor(diagnostics): remove bug report breadcrumbs
This commit is contained in:
@@ -8,14 +8,6 @@ export type InputFailure = {
|
||||
|
||||
export type InputResult<T> = { ok: true; value: T } | InputFailure;
|
||||
|
||||
export type NormalizedProperties = Record<string, string>;
|
||||
|
||||
export interface NormalizedBreadcrumb {
|
||||
name: string;
|
||||
timestampMillis: number;
|
||||
properties: NormalizedProperties;
|
||||
}
|
||||
|
||||
export interface NormalizedDevice {
|
||||
deviceName: string;
|
||||
deviceModel: string;
|
||||
@@ -36,16 +28,12 @@ export interface NormalizedBugPayload {
|
||||
contact: string;
|
||||
logs: string;
|
||||
device: NormalizedDevice;
|
||||
breadcrumbs: NormalizedBreadcrumb[];
|
||||
schemaVersion: 1;
|
||||
}
|
||||
|
||||
export const MAX_LOG_BYTES = 192 * 1024;
|
||||
export const MAX_BREADCRUMBS_JSON_BYTES = 16_000;
|
||||
export const MAX_DEVICE_JSON_BYTES = 4_000;
|
||||
|
||||
const MAX_PROPERTIES = 12;
|
||||
const MAX_BREADCRUMBS = 40;
|
||||
const MISSING = Symbol("missing");
|
||||
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
const UTF8_ENCODER = new TextEncoder();
|
||||
@@ -177,8 +165,6 @@ export function normalizeBug(body: JsonObject): InputResult<NormalizedBugPayload
|
||||
if (!logs.ok) return logs;
|
||||
const device = normalizeDevice(pick(body, ["device"]));
|
||||
if (!device.ok) return device;
|
||||
const breadcrumbs = normalizeBreadcrumbs(pick(body, ["breadcrumbs"]));
|
||||
if (!breadcrumbs.ok) return breadcrumbs;
|
||||
const version = schemaVersion(body);
|
||||
if (!version.ok) return version;
|
||||
|
||||
@@ -194,62 +180,10 @@ export function normalizeBug(body: JsonObject): InputResult<NormalizedBugPayload
|
||||
contact: contact.value,
|
||||
logs: includeLogs.value === true ? logs.value : "",
|
||||
device: device.value,
|
||||
breadcrumbs: breadcrumbs.value,
|
||||
schemaVersion: version.value,
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeBreadcrumbs(raw: unknown | typeof MISSING): InputResult<NormalizedBreadcrumb[]> {
|
||||
if (raw === MISSING) return success([]);
|
||||
if (!Array.isArray(raw)) return failure(400, "invalid_breadcrumbs");
|
||||
|
||||
const breadcrumbs: NormalizedBreadcrumb[] = [];
|
||||
for (const item of raw.slice(0, MAX_BREADCRUMBS)) {
|
||||
if (!isPlainObject(item)) return failure(400, "invalid_breadcrumbs");
|
||||
const name = stringField(item, ["name"], 64, "invalid_breadcrumbs", true, true);
|
||||
if (!name.ok) return name;
|
||||
const timestamp = timestampField(item, ["timestampMillis", "timestamp_millis", "ts"]);
|
||||
if (!timestamp.ok) return failure(400, "invalid_breadcrumbs");
|
||||
const properties = normalizeProperties(
|
||||
pick(item, ["properties", "props"]),
|
||||
"invalid_breadcrumbs",
|
||||
);
|
||||
if (!properties.ok) return properties;
|
||||
|
||||
breadcrumbs.push({
|
||||
name: name.value,
|
||||
timestampMillis: timestamp.value,
|
||||
properties: properties.value,
|
||||
});
|
||||
if (jsonBytes(breadcrumbs) > MAX_BREADCRUMBS_JSON_BYTES) {
|
||||
breadcrumbs.pop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return success(breadcrumbs);
|
||||
}
|
||||
|
||||
function normalizeProperties(
|
||||
raw: unknown | typeof MISSING,
|
||||
error: string,
|
||||
): InputResult<NormalizedProperties> {
|
||||
if (raw === MISSING) return success({});
|
||||
if (!isPlainObject(raw)) return failure(400, error);
|
||||
|
||||
const entries: Array<[string, string]> = [];
|
||||
const normalizedKeys = new Set<string>();
|
||||
for (const [key, value] of Object.entries(raw).slice(0, MAX_PROPERTIES)) {
|
||||
if (typeof value !== "string") return failure(400, error);
|
||||
const normalizedKey = truncateUtf8(key, 40);
|
||||
if (normalizedKey.length === 0 || normalizedKeys.has(normalizedKey)) {
|
||||
return failure(400, error);
|
||||
}
|
||||
normalizedKeys.add(normalizedKey);
|
||||
entries.push([normalizedKey, truncateUtf8(value, 128)]);
|
||||
}
|
||||
return success(Object.fromEntries(entries));
|
||||
}
|
||||
|
||||
function normalizeDevice(raw: unknown | typeof MISSING): InputResult<NormalizedDevice> {
|
||||
if (raw === MISSING) raw = {};
|
||||
if (!isPlainObject(raw)) return failure(400, "invalid_device");
|
||||
|
||||
@@ -32,12 +32,12 @@ export async function storeBug(
|
||||
try {
|
||||
const result = await database
|
||||
.prepare(
|
||||
`INSERT INTO bugs (
|
||||
id, received_at, occurred_at, install_id, app_version, platform,
|
||||
what_happened, expected, steps, contact, logs_r2_key,
|
||||
device_json, breadcrumbs_json, status, schema_version
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'open', ?)
|
||||
ON CONFLICT(id) DO NOTHING`,
|
||||
`INSERT INTO bugs (
|
||||
id, received_at, occurred_at, install_id, app_version, platform,
|
||||
what_happened, expected, steps, contact, logs_r2_key,
|
||||
device_json, status, schema_version
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'open', ?)
|
||||
ON CONFLICT(id) DO NOTHING`,
|
||||
)
|
||||
.bind(
|
||||
payload.id,
|
||||
@@ -52,7 +52,6 @@ export async function storeBug(
|
||||
payload.contact,
|
||||
logsKey,
|
||||
JSON.stringify(payload.device),
|
||||
JSON.stringify(payload.breadcrumbs),
|
||||
payload.schemaVersion,
|
||||
)
|
||||
.run();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
MAX_BREADCRUMBS_JSON_BYTES,
|
||||
MAX_DEVICE_JSON_BYTES,
|
||||
MAX_LOG_BYTES,
|
||||
normalizeBug,
|
||||
@@ -119,20 +118,11 @@ describe("normalizers", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps logs, breadcrumbs, and device JSON within valid byte budgets", () => {
|
||||
const properties = Object.fromEntries(
|
||||
Array.from({ length: 12 }, (_, index) => [`key-${index}-${"\u0000".repeat(40)}`, "\u0000".repeat(128)]),
|
||||
);
|
||||
const breadcrumbs = Array.from({ length: 40 }, (_, index) => ({
|
||||
name: `crumb-${index}`,
|
||||
timestamp_millis: index,
|
||||
properties,
|
||||
}));
|
||||
it("keeps logs and device JSON within valid byte budgets", () => {
|
||||
const result = normalizeBug(
|
||||
bugPayload({
|
||||
include_logs: true,
|
||||
logs: "😀".repeat(60_000),
|
||||
breadcrumbs,
|
||||
device: {
|
||||
device_name: "\u0000".repeat(200),
|
||||
device_model: "\u0000".repeat(200),
|
||||
@@ -145,14 +135,9 @@ describe("normalizers", () => {
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) return;
|
||||
const breadcrumbsJson = JSON.stringify(result.value.breadcrumbs);
|
||||
const deviceJson = JSON.stringify(result.value.device);
|
||||
expect(ENCODER.encode(result.value.logs).byteLength).toBe(MAX_LOG_BYTES);
|
||||
expect(ENCODER.encode(breadcrumbsJson).byteLength).toBeLessThanOrEqual(
|
||||
MAX_BREADCRUMBS_JSON_BYTES,
|
||||
);
|
||||
expect(ENCODER.encode(deviceJson).byteLength).toBeLessThanOrEqual(MAX_DEVICE_JSON_BYTES);
|
||||
expect(JSON.parse(breadcrumbsJson)).toEqual(result.value.breadcrumbs);
|
||||
expect(JSON.parse(deviceJson)).toEqual(result.value.device);
|
||||
});
|
||||
|
||||
@@ -218,7 +203,6 @@ function bugPayload(overrides: Record<string, unknown> = {}): Record<string, unk
|
||||
contact: "",
|
||||
logs: "",
|
||||
device: {},
|
||||
breadcrumbs: [],
|
||||
schema_version: 1,
|
||||
...overrides,
|
||||
};
|
||||
|
||||
@@ -121,7 +121,7 @@ describe("diagnostics Worker", () => {
|
||||
|
||||
const row = await env.DB.prepare(
|
||||
`SELECT occurred_at AS occurredAt, logs_r2_key AS logsKey,
|
||||
device_json AS deviceJson, breadcrumbs_json AS breadcrumbsJson
|
||||
device_json AS deviceJson
|
||||
FROM bugs WHERE id = ?`,
|
||||
)
|
||||
.bind(id)
|
||||
@@ -129,7 +129,6 @@ describe("diagnostics Worker", () => {
|
||||
occurredAt: number;
|
||||
logsKey: string;
|
||||
deviceJson: string;
|
||||
breadcrumbsJson: string;
|
||||
}>();
|
||||
expect(row?.occurredAt).toBe(3);
|
||||
expect(JSON.parse(row?.deviceJson ?? "null")).toEqual({
|
||||
@@ -139,9 +138,6 @@ describe("diagnostics Worker", () => {
|
||||
network: "offline",
|
||||
batteryLevel: "90%",
|
||||
});
|
||||
expect(JSON.parse(row?.breadcrumbsJson ?? "null")).toEqual([
|
||||
{ name: "opened", timestampMillis: 2, properties: {} },
|
||||
]);
|
||||
expect(await (await env.BLOBS.get(row?.logsKey ?? "missing"))?.text()).toBe("first logs");
|
||||
|
||||
const objects = await env.BLOBS.list({ prefix: `bugs/${id}/` });
|
||||
@@ -198,15 +194,15 @@ describe("diagnostics Worker", () => {
|
||||
`INSERT INTO bugs
|
||||
(id, received_at, occurred_at, install_id, app_version, platform,
|
||||
what_happened, expected, steps, contact, logs_r2_key,
|
||||
device_json, breadcrumbs_json, status, schema_version)
|
||||
VALUES (?, ?, ?, ?, '', '', 'failed', 'worked', '', '', ?, '{}', '[]', 'open', 1)`,
|
||||
device_json, status, schema_version)
|
||||
VALUES (?, ?, ?, ?, '', '', 'failed', 'worked', '', '', ?, '{}', 'open', 1)`,
|
||||
).bind(oldBugId, oldReceivedAt, oldReceivedAt, INSTALL_ID, oldBugKey),
|
||||
env.DB.prepare(
|
||||
`INSERT INTO bugs
|
||||
(id, received_at, occurred_at, install_id, app_version, platform,
|
||||
what_happened, expected, steps, contact, logs_r2_key,
|
||||
device_json, breadcrumbs_json, status, schema_version)
|
||||
VALUES (?, ?, ?, ?, '', '', 'failed', 'worked', '', '', NULL, '{}', '[]', 'open', 1)`,
|
||||
device_json, status, schema_version)
|
||||
VALUES (?, ?, ?, ?, '', '', 'failed', 'worked', '', '', NULL, '{}', 'open', 1)`,
|
||||
).bind(currentBugId, Date.now(), Date.now(), INSTALL_ID),
|
||||
]);
|
||||
|
||||
@@ -283,10 +279,10 @@ describe("diagnostics Worker", () => {
|
||||
INSERT INTO bugs (
|
||||
id, received_at, occurred_at, install_id, app_version, platform,
|
||||
what_happened, expected, steps, contact, logs_r2_key,
|
||||
device_json, breadcrumbs_json, status, schema_version
|
||||
device_json, status, schema_version
|
||||
)
|
||||
SELECT 'retention-backlog-' || printf('%04d', value), ?, ?, ?, '', '',
|
||||
'failed', 'worked', '', '', NULL, '{}', '[]', 'open', 1
|
||||
'failed', 'worked', '', '', NULL, '{}', 'open', 1
|
||||
FROM sequence`,
|
||||
)
|
||||
.bind(oldReceivedAt, oldReceivedAt, INSTALL_ID)
|
||||
@@ -358,7 +354,6 @@ function bugPayload(id: string, logs: string): Record<string, unknown> {
|
||||
network: "offline",
|
||||
batteryLevel: "90%",
|
||||
},
|
||||
breadcrumbs: [{ name: "opened", timestampMillis: 2, properties: {} }],
|
||||
schemaVersion: 1,
|
||||
};
|
||||
}
|
||||
@@ -382,7 +377,6 @@ function normalizedBug(id: string, logs: string): NormalizedBugPayload {
|
||||
network: "offline",
|
||||
batteryLevel: "90%",
|
||||
},
|
||||
breadcrumbs: [],
|
||||
schemaVersion: 1,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user