Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/hide-browser-request-noise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"miniflare": patch
"wrangler": patch
---

Hide requests the browser makes on its own from dev output

`wrangler dev` no longer logs `/favicon.ico` or Chrome DevTools' `/.well-known/appspecific/com.chrome.devtools.json` probe, so the request log reflects your app's traffic. The Local Explorer's Observability views leave the same requests out of the traces and logs lists.

Matching is on the exact path, so an app's own `/static/favicon.ico` is unaffected, and a hidden request still shows up if the Worker fails while serving it.
10 changes: 10 additions & 0 deletions .changeset/hide-vite-internals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"miniflare": patch
"wrangler": patch
---

Hide Vite's internal plumbing from the Observability views by default

Under the Vite plugin your Worker runs inside a runner Durable Object behind wrapper workers. Those spans, Vite's own module-init and export-type requests, and its RPC dispatch logs don't exist in production, so they're now hidden by default rather than sitting behind a UI toggle.

Failures are never hidden, and a plain `wrangler dev` session is unaffected.
1 change: 1 addition & 0 deletions packages/local-explorer-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@base-ui/react": "^1.1.0",
"@cloudflare/kumo": "^1.18.0",
"@cloudflare/workers-editor-shared": "^0.1.1",
"@cloudflare/workers-utils": "workspace:*",
"@codemirror/autocomplete": "^6.20.0",
"@codemirror/commands": "^6.10.2",
"@codemirror/lang-sql": "^6.10.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { describe, test } from "vitest";
import { beforeEach, describe, test, vi } from "vitest";
import { observabilityQuery } from "../../api";
import {
buildSpanTree,
buildWaterfall,
formatDuration,
formatLogMessage,
isRunning,
isViteWrapperSpan,
listEvents,
listTraces,
parseAttributes,
spanIsError,
stripDevRunnerSpans,
Expand All @@ -15,6 +18,11 @@ import {
} from "../../utils/observability";
import type { Span } from "../../utils/observability";

vi.mock("../../api", () => ({
observabilityQuery: vi.fn(),
observabilityClear: vi.fn(),
}));

function span(partial: Partial<Span>): Span {
return {
trace_id: "t",
Expand Down Expand Up @@ -150,6 +158,110 @@ describe("isViteWrapperSpan", () => {
});
});

describe("listEvents filtering", () => {
beforeEach(() => {
vi.mocked(observabilityQuery).mockReset();
vi.mocked(observabilityQuery).mockResolvedValue({
data: { result: { columns: [], rows: [] } },
} as never);
});

async function eventsSql(): Promise<string> {
await listEvents();
const call = vi.mocked(observabilityQuery).mock.calls.at(-1)?.[0];
return String((call as { body?: { sql?: string } })?.body?.sql ?? "");
}

test("hides favicon and DevTools-probe logs", async ({ expect }) => {
const sql = await eventsSql();
expect(sql).toContain("/favicon.ico");
expect(sql).toContain("com.chrome.devtools.json");
});

test("matches the noise path exactly, not as a URL suffix", async ({
expect,
}) => {
// A suffix match would also catch the app's own /static/favicon.ico and
// remote CDN favicons, so the path is extracted and compared exactly.
const sql = await eventsSql();
expect(sql).not.toContain("LIKE '%/favicon.ico'");
expect(sql).toMatch(/IN \([^)]*'\/favicon\.ico'[^)]*\)/);
expect(sql).toMatch(/IN \([^)]*'\/\.well-known\/[^)]*'[^)]*\)/);
});

test("judges noise on the trace's root request, not the emitting span", async ({
expect,
}) => {
const sql = await eventsSql();
expect(sql).toMatch(/EXISTS[\s\S]*r\.parent_id IS NULL/);
});

test("never hides an error-level log, or the logs of a failed request", async ({
expect,
}) => {
const sql = await eventsSql();
expect(sql).toContain("l.level = 'error' OR NOT EXISTS");
expect(sql).toMatch(/AND NOT COALESCE\([\s\S]*>= 500/);
});

test("only hides executeCallback logs when the trace is really Vite dev", async ({
expect,
}) => {
// A `wrangler dev` user can have their own executeCallback RPC method, so
// the shape rule must be paired with a wrapper-span check on the trace.
const sql = await eventsSql();
expect(sql).toMatch(
/executeCallback[\s\S]*EXISTS[\s\S]*v\.trace_id = l\.trace_id/
);
});

test("filters in the WHERE clause, before LIMIT", async ({ expect }) => {
const sql = await eventsSql();
const where = sql.indexOf("WHERE");
expect(where).toBeGreaterThan(-1);
expect(sql.indexOf("/favicon.ico")).toBeGreaterThan(where);
expect(sql.lastIndexOf("LIMIT ?")).toBeGreaterThan(
sql.indexOf("/favicon.ico")
);
});
});

describe("listTraces filtering", () => {
beforeEach(() => {
vi.mocked(observabilityQuery).mockReset();
vi.mocked(observabilityQuery).mockResolvedValue({
data: { result: { columns: [], rows: [] } },
} as never);
});

async function tracesSql(): Promise<string> {
await listTraces();
const call = vi.mocked(observabilityQuery).mock.calls.at(-1)?.[0];
return String((call as { body?: { sql?: string } })?.body?.sql ?? "");
}

test("hides Vite's own internal requests by path", async ({ expect }) => {
const sql = await tracesSql();
expect(sql).toContain("'/__vite_plugin_cloudflare'");
});

test("doesn't marker-match the root span, which would hide real traffic", async ({
expect,
}) => {
// Vite routes every request through __router-worker__, so a root-span
// marker match would blank out the user's own requests too.
const sql = await tracesSql();
expect(sql).not.toContain("__router-worker__");
});

test("keeps a hidden request visible when it failed", async ({ expect }) => {
// A 404 is how an unserved path answers, so only a 5xx (or an uncaught
// error) counts as a failure — otherwise the favicon noise comes back.
const sql = await tracesSql();
expect(sql).toMatch(/AND NOT COALESCE\([\s\S]*>= 500/);
});
});

describe("traceExtentMs", () => {
test("measures latest end minus earliest start", ({ expect }) => {
const spans = [
Expand Down
78 changes: 3 additions & 75 deletions packages/local-explorer-ui/src/routes/observability/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import {
Select,
} from "@cloudflare/kumo";
import {
EyeIcon,
EyeSlashIcon,
InfoIcon,
ListBulletsIcon,
MagnifyingGlassIcon,
Expand Down Expand Up @@ -39,9 +37,9 @@ import {
getTagKeys,
isObservabilityDisabledError,
isRunning,
isViteWrapperSpan,
listTraces,
operationLabel,
SHOW_VITE_INTERNALS,
spanIsError,
traceExtentMs,
visibleTraceSpans,
Expand All @@ -52,8 +50,6 @@ import type { Span, TraceRow } from "../../utils/observability";
import type { QueryClause } from "../../utils/observability-query";
import type { JSX } from "react";

const HIDE_DEV_RUNNER_KEY = "wobs-hide-dev-runner";

/** Display labels for the status/type filter dropdowns (excluding the "all" state). */
const STATUS_LABELS: Record<string, string> = {
success: "Success",
Expand Down Expand Up @@ -134,27 +130,6 @@ function ObservabilityView(): JSX.Element {
// trace list, and pause polling so we don't hammer a disabled endpoint.
const [disabled, setDisabled] = useState(false);
const [clearing, setClearing] = useState(false);
// Hide Vite dev module-runner plumbing spans (default on; persisted).
const [hideDevRunner, setHideDevRunner] = useState(() => {
try {
return localStorage.getItem(HIDE_DEV_RUNNER_KEY) !== "false";
} catch {
return true;
}
});
const toggleHideDevRunner = useCallback(() => {
setHideDevRunner((prev) => {
const next = !prev;
try {
localStorage.setItem(HIDE_DEV_RUNNER_KEY, String(next));
} catch {
// ignore
}
return next;
});
}, []);
// Session-only dismissal of the "running under Vite dev" notice.
const [viteNoticeDismissed, setViteNoticeDismissed] = useState(false);
// Traces whose inline log panel is expanded (keyed by traceKey).
const [showLogsFor, setShowLogsFor] = useState<Set<string>>(new Set());
const toggleLogs = useCallback((key: string) => {
Expand Down Expand Up @@ -336,16 +311,6 @@ function ObservabilityView(): JSX.Element {
[traces]
);

// Detect a Vite dev session from the data: Vite routes every request through
// its router/asset workers, so any HTTP trace is rooted in an infra worker.
// (Internally-triggered traces — alarm/queue/cron — are rooted in the user
// worker, but a real session always has at least one request too.)
const isViteDev = useMemo(
() => traces.some((t) => isViteWrapperSpan(t)),
[traces]
);
const showViteNotice = isViteDev && !viteNoticeDismissed;

if (disabled) {
return <ObservabilityDisabled />;
}
Expand All @@ -361,20 +326,6 @@ function ObservabilityView(): JSX.Element {
</span>
</div>
<div className="flex-1" />
<Button
size="sm"
variant={hideDevRunner ? "secondary" : "ghost"}
icon={hideDevRunner ? EyeIcon : EyeSlashIcon}
title={
hideDevRunner
? "Show Vite dev module-runner plumbing spans (vite dev only)"
: "Hide Vite dev module-runner plumbing spans (vite dev only)"
}
aria-pressed={hideDevRunner}
onClick={toggleHideDevRunner}
>
{hideDevRunner ? "Show Vite runner spans" : "Hide Vite runner spans"}
</Button>
<ClearButton
onConfirm={handleClear}
loading={clearing}
Expand All @@ -388,29 +339,6 @@ function ObservabilityView(): JSX.Element {
/>
</header>

{showViteNotice ? (
<div className="flex items-center gap-2 border-b border-kumo-fill bg-blue-500/10 px-6 py-2 text-xs text-kumo-default">
<InfoIcon size={14} className="shrink-0 text-blue-500" />
<span className="flex-1">
{hideDevRunner
? "Running under Vite dev — some spans come from Vite's module runner (module loading, RPC dispatch) rather than your code. They're hidden by “Hide Vite runner spans”."
: "Running under Vite dev — Vite module-runner spans (module loading, RPC dispatch) are shown and may add noise to your traces."}
</span>
<Button size="sm" variant="secondary" onClick={toggleHideDevRunner}>
{hideDevRunner
? "Show Vite runner spans"
: "Hide Vite runner spans"}
</Button>
<Button
size="sm"
variant="ghost"
icon={XIcon}
aria-label="Dismiss notice"
onClick={() => setViteNoticeDismissed(true)}
/>
</div>
) : null}

{/* filter bar — a simpler version of the dashboard query builder */}
<div className="flex items-center gap-2 border-b border-kumo-fill px-6 py-3">
<InputGroup size="sm" className="flex-1">
Expand Down Expand Up @@ -551,7 +479,7 @@ function ObservabilityView(): JSX.Element {
// e.g. an alarm's ~15s runner-dispatch span as its duration.
const shownSpans =
isSel && spans.length
? visibleTraceSpans(spans, hideDevRunner)
? visibleTraceSpans(spans, !SHOW_VITE_INTERNALS)
: spans;
const shownDur =
isSel && spans.length ? traceExtentMs(shownSpans) : dur;
Expand Down Expand Up @@ -660,7 +588,7 @@ function ObservabilityView(): JSX.Element {
rootSpanId={t.root_span_id}
traceDurationMs={shownDur}
invocationRootIds={invocationRootsByTrace[key]}
hideDevRunner={hideDevRunner}
hideDevRunner={!SHOW_VITE_INTERNALS}
/>
{showLogsFor.has(key) ? (
<InvocationLogs traceId={t.trace_id} />
Expand Down
Loading
Loading