[DRAFT] [wrangler] Experimental local-dev observability (traces, spans, logs + MCP)#14391
Draft
nickhilpat wants to merge 40 commits into
Draft
[DRAFT] [wrangler] Experimental local-dev observability (traces, spans, logs + MCP)#14391nickhilpat wants to merge 40 commits into
nickhilpat wants to merge 40 commits into
Conversation
Adds a top-level Observability tab to the local dev explorer that renders a trace waterfall, modeled on the Workers Observability dashboard's Traces page. - src/lib/traces.ts: read the local trace store (D1 traces/spans tables) via the existing D1 raw-query endpoint; discover the trace DB from worker bindings; build the span tree from parent_id. - src/components/observability/TraceWaterfall.tsx: two-column waterfall (span-name tree + timeline) with gridlines, nice time axis, state-colored bars, kind dots, hover/selection, and a span attributes detail panel. - src/routes/observability.tsx: trace list (duration gauge + status badges) with click-to-expand waterfall. - Sidebar: top-level Observability entry. Reuses the explorer's existing D1 read path - no backend/openapi changes.
Self-contained local-dev tracing prototype that feeds the Observability tab: a demo Worker plus a tailStream collector that persists traces to a local D1 (with the trace-query helper). Lives under experiments/ (not a workspace member) so it doesn't couple into the monorepo install/build. See RUNNING-IN-WORKERS-SDK.md for how to run it with the locally built wrangler.
What this branch is, what's built (Observability tab + prototype), how it works, how to run it, and the remaining TODOs.
- List view restyled to match the Workers Observability dashboard: Timestamp | Operation | Duration (ms) | Spans | Errors columns, left accent bar, stacked duration cell with gauge, dotted-underline timestamps. - Waterfall ported faithfully (dashboard span-kind icons, two-column timeline, marker lines, state-colored bars, collapse). - Filtering (a simpler query builder): free-text search over operation/span names/attributes, status + type (span kind) dropdowns, and tag filters (attribute key + value) via SQLite json_each over the persisted attributes - no new storage required.
Replace native selects with the app's DropdownMenu (trigger button + menu items with check marks), matching the dashboard's filter dropdown look, for the status / type / tag-key filters.
- Collector now persists console.log events to a new D1 'logs' table (level, message, operation, span) on outcome. - New Events route: list (timestamp / level / message / operation) with search + level filter; click a row to expand the full event JSON (with copy), mirroring the dashboard's events view. - Extract shared FilterSelect (Kumo DropdownMenu); sidebar now has Traces + Events entries.
Collapse the two sidebar entries into one Observability item and add a title dropdown to switch between the Traces and Events views, preserving the selected worker in the URL.
Parse a simple AND-only query syntax in the Traces and Events search bars (status:, kind:, dur:>N, <attr>:value, level:, op:) on top of the existing free-text search, reusing the D1 filter builder.
…anel trace-collector: TRACE_FORMAT (pretty|agent|json) for token-light trace output and TRACE_LOG_LEVEL filtering. wobs-trace-demo: click-to-fire control panel served at / for generating traces.
Productizes the local trace prototype into the platform: with X_LOCAL_OBSERVABILITY=true, wrangler dev and the Vite plugin auto-inject an internal trace collector (embedded miniflare worker) as a streaming-tail consumer of user workers, set the required compat flags, and provision an internal WOBS_TRACES D1 the collector persists to and the Local Explorer reads. - miniflare: unsafeObservability option, embedded collector worker, collector service registration (resolved via getUserServiceName so user workers' streamingTails reference resolves), explicit D1 binding to the internal store. - wrangler dev + vite plugin: gate on X_LOCAL_OBSERVABILITY; add compat flags, the internal D1, and the collector streamingTail to user workers. - workers-utils: X_LOCAL_OBSERVABILITY gate + shared collector/D1 constants. No user config needed - traces are captured for any worker on either dev server.
Tests across all packages: - local-explorer-ui: query parser + traces SQL-builder/pure-fn unit tests - miniflare: integration spec (capture -> persist via unsafeObservability) - wrangler + vite-plugin: X_LOCAL_OBSERVABILITY gating unit tests Local Explorer Observability tab: - Multi-expand trace rows (toggle open/close, multiple waterfalls at once) - Stable composite keys (trace_id + root_span_id) to fix collapse flicker on auto-refresh for distributed traces that share a trace_id - Exact WOBS_TRACES discovery; hide the internal trace store from the D1 list Other: - Extract applyLocalObservability helper in the Vite plugin (testable) - Filter the internal trace D1 from the explorer listD1Databases endpoint - Remove the wobs-local-traces prototype + branch notes; fix stale collector comment
…W ingestion Bring the local collector's tail-stream ingestion to parity with vega's cf-to-otel streaming-tail worker: - Name + classify the root span for every trigger type (fetch, scheduled, alarm, queue, email, trace, hibernatableWebSocket, jsrpc, custom) instead of only fetch - Capture per-trigger onset attributes (faas.trigger, faas.cron, cloudflare.scheduled_time, cloudflare.queue.*, cloudflare.email.*, cloudflare.trace.count, http.request.method, url.full) - Capture worker/invocation metadata (script_name, entrypoint, execution_model, dispatch_namespace, script_version.*, script_tags) - Capture cpu_time_ms / wall_time_ms and cloudflare.outcome on the root span - Capture exception stacktraces Adds a spec assertion covering the enriched root-span attributes.
Add a Clear button (next to Refresh) that wipes all captured traces/spans/logs from the local trace store, with an "are you sure?" confirmation dialog and a "Don't show this message again" opt-out persisted in localStorage. Disabled when there are no traces. Adds a `clearTraces` helper + unit test.
…ility Match the production model where a trace is identified by traceId and the root is the parent-less span, so a request flowing through multiple worker invocations renders as one stitched trace. - Collector stores absolute span/log timestamps (so invocations sharing a traceId share a timeline) and records the root span's parent_span_id; adds a best-effort schema migration for existing stores - listTraces returns one row per distributed trace (the parent-less root invocation) with whole-trace span counts/durations - buildSpanTree re-bases absolute times to the trace start; the waterfall derives its window from the spans so sub-invocations past the root are covered Verified working for the Vite multi-worker case: in the multi-worker playground (worker-a -> worker-b via service binding, both in one miniflare instance via auxiliaryWorkers) the cross-worker call stitches into a single trace (one row, multiple invocations across both workers). Cross-process `wrangler dev` (separate miniflare instances) does not stitch, because workerd does not propagate span context across the dev-registry socket; prod-parity local multi-worker tracing therefore requires the workers to share one miniflare instance, which the Vite plugin's auxiliary workers already do.
Port the local-observability MCP feature from the local-obs-mcp branch: - MCP page (/mcp): agent access-control config (log levels + per-resource allow-list, mirrored to an mcp_config table), copy-paste connect snippets for opencode / Claude Code / Cursor (+ Add-to-Cursor deeplink), and an agent activity log (reads mcp_calls) - Surfaced as a third view in the Observability switcher (Traces / Logs / MCP) rather than a separate sidebar item, so it lives under Observability - lib/mcp.ts: config + call-history data layer over the explorer's D1 endpoint - Relocated the dependency-free stdio MCP server into the repo at packages/local-explorer-ui/mcp-server/ (was the deleted prototype dir) and made its path user-configurable on the Connect card (persisted in localStorage) instead of a hardcoded absolute path The server exposes list_recent_errors, explain_trace, and search_logs.
Ship the stdio MCP server with the explorer and surface its absolute path to the Observability MCP page, so the connect snippets (opencode/Claude/Cursor) are copy-paste-ready with no manual path entry. - miniflare build copies mcp-server.mjs into dist/local-explorer-ui - the core explorer plugin injects the bundled server's absolute path as a binding, and the explorer worker exposes it at GET /api/local/mcp - the MCP page fetches it and uses it as the default server path; the field is still editable and an explicit override persists in localStorage
…erfall
Multi-worker / multi-invocation distributed traces stitch into a single
waterfall, but without per-worker labels (workerd doesn't surface scriptName
locally) it was hard to see where one worker hands off to another. Mark each
invocation root other than the trace's top-level root with a dashed divider and
an "inv" badge ("Start of a new worker invocation"), so the boundaries between
invocations/workers are visually obvious.
- getInvocationRootIds(): root span ids of every invocation sharing a trace_id
(from the per-invocation trace rows)
- the Observability tab fetches them per expanded trace and the waterfall marks
spans that begin a new invocation
…er boundaries In Vite dev, user workers run inside Vite's module-runner Durable Object (cloudflare.entrypoint = "__VITE_RUNNER_OBJECT__"), so each invocation is wrapped by runner plumbing that showed up as spurious invocation-boundary markers. Exclude those runner invocations from getInvocationRootIds so the "inv" markers only flag real worker handoffs.
In `vite dev`, user code runs inside Vite's module-runner Durable Object, so every invocation is wrapped with a durable_object_subrequest -> jsrpc (executeCallback on __VITE_RUNNER_OBJECT__) chain that isn't the user's code. Add a "Hide runner spans" toggle to the Observability tab (default on, persisted in localStorage) that strips those runner spans plus the DO-subrequest that dispatches into them and re-parents the real children up, so the waterfall shows only the worker's actual spans. No-op outside Vite dev. - stripDevRunnerSpans() in lib/traces + a hideDevRunner param on buildSpanTree - TraceWaterfall threads hideDevRunner through - unit tests for the strip/re-parent logic
Move the MCP page out of the Observability view switcher (Traces/Logs/MCP) back into its own left-hand sidebar entry, so Observability stays focused on traces/logs and MCP is a sibling tab.
Add a user-facing `wrangler dev --experimental-observability` opt-in for local observability capture, as a cleaner alternative to the internal X_LOCAL_OBSERVABILITY env var (the flag sets that env var, which the miniflare dev wiring already reads). Declares X_LOCAL_OBSERVABILITY in turbo.json.
🦋 Changeset detectedLatest commit: cf840ce The changes in this PR will be included in the next version bump. This PR includes changesets to release 6 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
…periment Clone-and-build helper + usage doc (wrangler dev flag and Vite env-var paths) so people can try the experimental local-dev observability feature from this fork without an npm publish/prerelease.
@cloudflare/autoconfig
create-cloudflare
@cloudflare/deploy-helpers
@cloudflare/kv-asset-handler
miniflare
@cloudflare/pages-shared
@cloudflare/unenv-preset
@cloudflare/vite-plugin
@cloudflare/vitest-pool-workers
@cloudflare/workers-auth
@cloudflare/workers-editor-shared
@cloudflare/workers-utils
wrangler
commit: |
…aude/Cursor Adds Install buttons on the MCP tab that write project-local agent config (.opencode/opencode.json, .mcp.json, .cursor/mcp.json) via a new explorer endpoint (POST /api/local/mcp/install) that forwards to a miniflare loopback handler. The bundled mcp-server.mjs path comes from the trusted LOCAL_EXPLORER_MCP_SERVER_PATH binding; existing config is merged non-destructively.
The draft PR's pkg.pr.new prereleases (npm i https://pkg.pr.new/cloudflare/workers-sdk/wrangler@14391) are now the way to try this, so the root OBSERVABILITY.md + setup.sh fork-clone helpers are no longer needed.
The install buttons wrote the agent config correctly but only surfaced a toast on success, which wasn't reliably visible — so it looked like nothing happened. Add an inline status line (green check + written path, or red error) under the button, matching the copy-button pattern and independent of the toast portal. Clears when switching agents.
New CLI commands (logs / traces / trace / query / skill) that read the local-dev trace store captured by `wrangler dev --experimental-observability`. They read the persisted SQLite database directly and read-only via Node's built-in node:sqlite, so they work whether or not the dev server is running, without starting a second workerd. The `query` command lets an agent run arbitrary read-only SQL (codemode-style) and `skill` prints schema + example queries. Also fixes a latent type error in `pages dev` exposed by the new typecheck.
The `wrangler observability` CLI is now the primary way to inspect captured traces and logs. The bundled MCP server is gated behind X_LOCAL_OBSERVABILITY_MCP=true (off by default) across wrangler, miniflare, and the Vite plugin; when disabled, the server path isn't exposed, the install endpoint refuses, and the Local Explorer hides the MCP tab. The MCP page is reframed as an optional alternative for agents that prefer connecting over MCP rather than running CLI commands.
…detected agents Reuses the same agent detection as `wrangler --install-skills` (rosie) to write the observability guidance as a SKILL.md (with frontmatter) into each detected AI agent's global skills directory, so agents auto-discover the `wrangler observability` CLI when debugging a local Worker. Without --install the command still prints the guidance to stdout.
…output Ports the Local Explorer's stripDevRunnerSpans logic to the CLI: `observability traces` and `observability trace` now hide the Vite module-runner plumbing (the durable_object_subrequest -> jsrpc into __VITE_RUNNER_OBJECT__ chain) by default and re-parent the real spans, with --include-runner-spans to show everything. No-op for wrangler dev (no marker). Also aligns the traces list span_count with the Explorer UI (whole-trace COUNT of spans rather than the stored column).
…n the skill
Adds a first-class cloudflare({ experimental: { observability: true } }) option to enable local-dev observability capture in Vite (equivalent to X_LOCAL_OBSERVABILITY=true), and updates the wrangler observability skill to tell agents how to enable capture under Vite and to run the project-local wrangler.
Hosts an MCP endpoint at /cdn-cgi/explorer/mcp (Streamable HTTP) when MCP is opted in (X_LOCAL_OBSERVABILITY_MCP). Instead of feeding the agent the full OpenAPI spec, it exposes a 'run' tool: the agent submits a JS snippet that calls a 'cf' client (D1/KV/R2/DO/Workflows/traces) and returns only what it needs. Snippets execute in-process via a workerd UnsafeEval binding (injected only when MCP is enabled), and the cf client dispatches to the explorer's own API routes in-process via Hono app.request — no network round-trips, no separate install. An explorer_api tool returns a compact cheatsheet + the OpenAPI URL.
Enforce the Agent Access config inside the Miniflare-hosted Codemode MCP cf client: log-level policy for trace logs, per-resource allowlists for D1/KV/DO/R2, and raw Explorer API access disabled by default. Audit run calls with the submitted code, access attempts, and result/error summary.
Point agent setup at the Miniflare-hosted /cdn-cgi/explorer/mcp endpoint instead of a local stdio server path. Keep Agent Access controls, add the raw Explorer API access toggle, and detect the hosted MCP endpoint for showing the optional MCP view.
Drop the packaged stdio MCP server, Miniflare build copy, one-click install backend, and server-path binding now that the supported MCP path is the hosted Codemode endpoint in Local Explorer.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds local-dev observability: when enabled,
wrangler devand the Cloudflare Vite plugin automatically capture structured traces (spans + logs) for whatever Worker you're developing — no extra config — and surface them in the Local Explorer's new Observability tab.Enable it
wrangler dev --experimental-observabilityX_LOCAL_OBSERVABILITY=true vite devX_LOCAL_OBSERVABILITYgate)Then open
http://localhost:<port>/cdn-cgi/explorer/→ Observability.What you get
traceId(service bindings, Durable Objects, Vite auxiliary workers) stitch into a single trace, mirroring the production model (group bytraceId, parent-less root). Invocation boundaries are delineated in the waterfall, with a toggle to hide Vite dev module-runner plumbing spans.How it works
miniflareinjects an internal streaming-tail collector (behindunsafeObservability) that persists traces/spans/logs to an internal D1 store; capture parity with the production STW (cf-to-otel) ingestion.wrangler(dev) and@cloudflare/vite-pluginwire the collector + store onto the user worker(s) when the flag/env is set.@cloudflare/local-explorer-uiadds the Observability + MCP UI (bundled into miniflare).Scope / safety
--experimental-observability/X_LOCAL_OBSERVABILITYis set.--experimental-observability; public docs will follow if/when it graduates from experimental.