Cloudflare Workers Observability Tutorial 2026

May 17, 2026

Cloudflare Workers Observability Tutorial 2026

To get production-grade observability into a Cloudflare Worker in 2026, enable Workers Logs in wrangler.toml, log structured JSON instead of strings, drop head_sampling_rate to manage cost on high-traffic Workers, and export OTel traces and logs to Sentry through the new Cloudflare destination pipelines. This tutorial wires up all four — with a runnable Worker, a wrangler tail demo, the exact Wrangler block for Sentry export, and the dollar math for a 15M-request-per-month Worker — on wrangler@4.92.01 and @sentry/cloudflare@10.53.12.

TL;DR

  • Workers Logs is on by default for newly created Workers but only ingests once you add [observability] enabled = true to your Wrangler config.3
  • The Workers Free plan gets 200,000 log events per day with 3-day retention; the Paid plan bundles 20 million per month with 7-day retention and charges $0.60 per additional million.3
  • head_sampling_rate = 0.1 keeps 10% of invocations and drops cost roughly 10× on a high-volume Worker — invocations are sampled, not individual log lines, so you still get the full trace for sampled requests.3
  • For real-time terminal debugging, wrangler tail --status error streams only failing invocations and side-steps sampling mode on busy Workers.4
  • To get traces and logs into Sentry, add a Cloudflare-side destination once in the dashboard, then point [observability.traces] and [observability.logs] at it from wrangler.toml — no Sentry SDK required if you only need OTel-shaped data.5
  • If you want exception capture, breadcrumbs, and release tracking with the Sentry SDK API surface, install @sentry/cloudflare@10.53.1, wrap your handler with Sentry.withSentry, and set compatibility_flags = ["nodejs_compat"].26

What you'll learn

  • How to enable Workers Logs in wrangler.toml with the correct observability block
  • How head_sampling_rate actually samples requests (not log lines) and how to tune it
  • How to write structured-JSON logs so the Workers dashboard auto-indexes the fields
  • How to stream live logs with wrangler tail and filter to the requests you care about
  • How to choose between Workers Logs, Tail Workers, and Workers Logpush for forwarding logs off-platform
  • How to export OTel traces and logs from a Worker to a Sentry project using Cloudflare destinations
  • How to add the @sentry/cloudflare SDK for exception capture, breadcrumbs, and trace context
  • How to size the bill for a 15M-request-per-month Worker after the March 1, 2026 traces-billing change7

Prerequisites

You need:

  • Node.js 24 LTS (Active) or Node.js 22 LTS (Maintenance) — both are supported by Wrangler v4.8
  • A Cloudflare account — Free is enough to follow the Workers Logs and wrangler tail sections; Workers Paid ($5/month minimum) is required for the OTel export and Tail Workers steps.9
  • A Sentry account — Sentry's Developer plan is free and includes 5,000 events per month with 30-day retention, which is more than enough to follow this tutorial. The OTLP traces and logs endpoints we use in Step 5 are currently in open beta on the Sentry side.610
  • macOS, Linux, or Windows with WSL — Wrangler runs the dev server through Workerd; native Windows works but WSL is the path the docs assume.

Install the toolchain:

# Pin Wrangler to a specific patch — `latest` drifts week to week
npm install --save-dev wrangler@4.92.0
npx wrangler --version
# Expected: ⛅️ wrangler 4.92.0

You'll also need a deployed Worker route to see Workers Logs in the dashboard — wrangler dev shows console output locally, but the Logs UI only renders production invocations.

Step 1: Scaffold a Worker that emits realistic logs

Spin up a tutorial Worker with the Hello-World TypeScript template. We'll add structured logs, a deliberate error path, and a slow path so the data we send to Sentry actually has something interesting in it.

npm create cloudflare@2.68.2 -- nlt-observability-demo \
  --type=hello-world --lang=ts --git --no-deploy
cd nlt-observability-demo

Replace src/index.ts with a handler that logs at three levels and exposes routes for debugging:

// src/index.ts
export interface Env {
  ENVIRONMENT: string;
}

function logEvent(level: "info" | "warn" | "error", event: Record<string, unknown>) {
  // Always log a plain object — the Workers Logs UI auto-indexes the fields.
  const payload = {
    level,
    timestamp: new Date().toISOString(),
    ...event,
  };
  if (level === "error") {
    console.error(payload);
  } else if (level === "warn") {
    console.warn(payload);
  } else {
    console.log(payload);
  }
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    // Cloudflare augments Request with a `cf` property at runtime; the type
    // ships in @cloudflare/workers-types under IncomingRequestCfProperties.
    const cf = (request as unknown as { cf?: { country?: string } }).cf;
    const requestId = crypto.randomUUID();

    logEvent("info", {
      msg: "request received",
      request_id: requestId,
      method: request.method,
      path: url.pathname,
      country: cf?.country ?? "unknown",
      environment: env.ENVIRONMENT,
    });

    if (url.pathname === "/slow") {
      await new Promise((r) => setTimeout(r, 250));
      logEvent("warn", {
        msg: "slow path hit",
        request_id: requestId,
        wait_ms: 250,
      });
      return new Response("slow response\n");
    }

    if (url.pathname === "/boom") {
      logEvent("error", {
        msg: "user triggered boom",
        request_id: requestId,
        path: url.pathname,
      });
      throw new Error("Intentional /boom error for observability testing");
    }

    return new Response(`hello from ${env.ENVIRONMENT}\n`);
  },
};

Two things are worth noting before we go further.

First, the logger always emits an object, never a template-string. In the Workers Logs UI, console.log({ user_id: 123 }) is indexed under the user_id field, while console.log("user_id: " + 123) is stored as a single opaque message you can only text-search.3 Searching a 50 GB log volume by indexed field is fast; text-searching it is not.

Second, every log line carries request_id. Workers Logs already tags every event with a Cloudflare-side trace ID, but adding your own request_id lets the same identifier appear in your downstream Sentry traces, your application database, and any third-party APIs the Worker calls — making cross-system correlation trivial.

Step 2: Enable Workers Logs in wrangler.toml

Replace the generated wrangler.toml with the production-shaped config below. The observability block is the only one strictly required, but pinning a compatibility date and naming environments early saves migration pain later.

# wrangler.toml
name = "nlt-observability-demo"
main = "src/index.ts"
compatibility_date = "2026-05-15"
compatibility_flags = ["nodejs_compat"]

[vars]
ENVIRONMENT = "development"

[observability]
enabled = true
head_sampling_rate = 1  # 100% — sample everything in dev

[env.staging]
name = "nlt-observability-demo-staging"
vars = { ENVIRONMENT = "staging" }

[env.staging.observability]
enabled = true
head_sampling_rate = 1

[env.production]
name = "nlt-observability-demo-prod"
vars = { ENVIRONMENT = "production" }

[env.production.observability]
enabled = true
head_sampling_rate = 0.1  # 10% — high-volume cost control

Three details to grasp:

  • observability.enabled = true is required to write logs to Workers Logs. The dashboard toggle defaults to on for newly created Workers, but Cloudflare's own docs are explicit that you must add the setting to your Wrangler file and redeploy for the Worker to actually start ingesting events.3
  • head_sampling_rate accepts any value in the range 0 to 1. If the rate is 0.1, Cloudflare flips a per-invocation coin: 10% of invocations have all their logs stored, 90% are discarded entirely. This is fundamentally different from line-level sampling — your sampled invocations are intact and traceable from request received to response.3
  • The nodejs_compat flag isn't strictly needed for Workers Logs, but the @sentry/cloudflare SDK we'll add in Step 6 requires it for AsyncLocalStorage support.6 Setting it now avoids a redeploy later.

Deploy to staging and run a few requests:

npx wrangler deploy -e staging
curl -i https://nlt-observability-demo-staging.<your-subdomain>.workers.dev/
curl -i https://nlt-observability-demo-staging.<your-subdomain>.workers.dev/slow
curl -is https://nlt-observability-demo-staging.<your-subdomain>.workers.dev/boom | head -1
# Expected: HTTP/2 500

In the Cloudflare dashboard, navigate to Workers & Pages → your Worker → Observability. Within about 30 seconds you should see three invocation logs, one of them red. Click an invocation and the right pane shows your structured fields (request_id, country, environment) as searchable filters — not as a wall of text. This is the indexing payoff from Step 1.

Step 3: Stream live logs with wrangler tail

For active debugging, wrangler tail is faster than the dashboard. It streams structured events to your terminal over a WebSocket, and accepts filters that narrow the firehose before it reaches your shell.

# Stream every invocation
npx wrangler tail -e staging

# In another shell, hit the Worker a few times
curl https://nlt-observability-demo-staging.<your-subdomain>.workers.dev/
curl -s https://nlt-observability-demo-staging.<your-subdomain>.workers.dev/boom > /dev/null

wrangler tail works on the Free plan and has no per-request cost, but a high-traffic Worker can push the tail into sampling mode — Cloudflare drops messages and warns you in the stream when this happens.4 Filters help by reducing the message volume server-side before sampling kicks in:

# Only invocations where the Worker outcome was "error"
# (uncaught exceptions; the documented enum is ok | error | canceled[^18])
npx wrangler tail -e staging --status error

# Only POST + outcome=error
npx wrangler tail -e staging --method POST --status error

# Match a specific request — useful when you embed request_id in URLs
npx wrangler tail -e staging --search "abc-123-request-id"

Note that --status error filters by the invocation outcome Cloudflare records, not the HTTP status code your Worker returned. A handler that returns new Response("not found", { status: 404 }) is a successful invocation with outcome=ok; a handler that throws is an outcome=error; a client disconnect mid-response is outcome=canceled.11 If you want to filter by HTTP status, use a --search term on a structured log field instead.

A pragmatic on-call workflow is to keep wrangler tail --status error -e production running in a tmux pane during deploys. The moment a release starts emitting exceptions, the stack appears in your terminal with the full structured log object — no dashboard click-through.

A behavioural detail worth knowing: when you change the filter mid-stream (some teams script this via tail --search swaps), the new filter takes up to 60 seconds to take effect on the server side.12 If you don't see filtered events immediately, wait a minute before assuming something is broken.

Step 4: Choose between Workers Logs, Tail Workers, and Logpush

Cloudflare offers three distinct log-handling primitives, and the right choice is rarely "all of them at once." Most tutorials cover them in isolation; here is the decision matrix laid out side-by-side:

PrimitiveWhere logs landTierUse when
Workers LogsCloudflare-managed store, queryable in dashboard for 3–7 daysFree + PaidFirst-line observability; you want to query without leaving Cloudflare.
Tail WorkersAnother Worker (the tail() handler) that you writePaid + Enterprise13You need bespoke routing/transformation before logs leave Cloudflare — for example, scrub PII, dual-write to two destinations, or send to a SIEM with custom auth.
Workers LogpushDatadog, New Relic, Splunk, S3, GCS, Azure Blob, othersPaid14You already have a log warehouse and want hands-off delivery at $0.05/M delivered events.14
OTel export to Sentry / Honeycomb / Axiom / Grafana / PostHogOTLP destination configured in Cloudflare dashboardPaidYou want OTel-shaped traces and logs in a tracing tool, with no SDK in your code.

The matrix matters for cost. Workers Logs alone bills per ingested event ($0.60/M after the 20M Paid bundle). Logpush adds $0.05/M delivered events on top. Tail Workers bill by their own CPU time, not request count — a tail handler that hits a slow external API can outpace your producer Worker's bill if you're not careful.15

If you're starting fresh in 2026 and your destination is Sentry (or Honeycomb, Axiom, Grafana Cloud, PostHog), skip Tail Workers and Logpush entirely and go straight to the OTel export route in Step 5. The OTel pipelines were the explicit reason Cloudflare reorganized the observability stack in early 2026.16

Step 5: Export OTel traces and logs to Sentry (no SDK required)

This is the 2026-shaped path most tutorials miss: you can stream the same telemetry that powers the Cloudflare dashboard out to Sentry without adding a single line of JavaScript to your Worker. The Worker keeps using console.log, Cloudflare's automatic tracing instruments every fetch handler invocation and outbound subrequest once you opt in with [observability.traces], and the new "Pipelines" feature in Workers Observability forwards both streams to a Sentry OTLP endpoint.

Heads-up before you wire this up: Sentry's OTLP traces and logs ingest is in open beta as of May 2026 — it's available to all customers, but the surface area can change.10 Cloudflare's automatic tracing is also still labelled Beta. During the beta, observability.enabled = true alone turns on logs only; traces require the additional [observability.traces] block we add in Step 5c.7 Billing for tracing usage began March 1, 2026.7

5a. Create a Sentry project and copy your OTLP details

In Sentry, navigate to Insights → Projects → New Project, pick the platform that matches what you'd otherwise instrument (any JavaScript flavor is fine), and create it. Then go to Settings → Projects → <your project> → Client Keys (DSN) and scroll to OpenTelemetry (OTLP).5 You'll see two endpoints and one authentication header:

OTLP Traces Endpoint: https://o0000000.ingest.sentry.io/api/0000000/integration/otlp/v1/traces
OTLP Logs Endpoint:   https://o0000000.ingest.sentry.io/api/0000000/integration/otlp/v1/logs
Header:               x-sentry-auth: sentry sentry_key=abcd1234...

The sentry_key= value is your Sentry project's public key (the same key inside the DSN), not a secret token — it identifies the project to Sentry's ingest layer.

5b. Add the destinations in the Cloudflare dashboard

In the Cloudflare dashboard, go to Workers & Pages → Observability → Pipelines and click Add destination twice — once for traces, once for logs.5

For the traces destination:

  • Destination Name: sentry-traces
  • Destination Type: Traces
  • OTLP Endpoint: the OTLP Traces Endpoint from Sentry
  • Custom Header: x-sentry-authsentry sentry_key={SENTRY_PUBLIC_KEY} (paste the exact string Sentry showed you)

For the logs destination:

  • Destination Name: sentry-logs
  • Destination Type: Logs
  • OTLP Endpoint: the OTLP Logs Endpoint
  • Custom Header: x-sentry-auth → same value as above

Save each destination — the dashboard surfaces an error if the endpoint is unreachable or the auth header is malformed, so common typos are caught before the Worker ever sends data.

5c. Point your Worker at the destinations

Add the trace and log routing to wrangler.toml. The exact block shape comes straight from Cloudflare's "Export to Sentry" page:5

# Append to wrangler.toml
[observability.traces]
enabled = true
destinations = ["sentry-traces"]  # Must match the Cloudflare dashboard name

[observability.logs]
enabled = true
destinations = ["sentry-logs"]

Deploy to production:

npx wrangler deploy -e production

If you only want OTel export turned on for the production environment, the same blocks nest under [env.production.observability.traces] and [env.production.observability.logs]. Destinations are configured once in the Cloudflare dashboard at the account level, so the same name (sentry-traces) resolves the same way regardless of which environment deploys it.5

Hit the Worker a few times and check Sentry. The docs warn it may take a few minutes after the first deploy for data to appear.5 Once it does, navigate to Explore → Traces in Sentry — you'll see a trace per Worker invocation, with spans for any fetch subrequests the Worker made. Explore → Logs shows the structured JSON we set up in Step 1, with the same fields (request_id, country, environment) now searchable inside Sentry too.

A behavioural detail you'll want to flag in your runbook: Cloudflare's tracing-billing change activated on March 1, 2026. Each span is now metered against the same per-million quota and price as logs — 20M included on Paid, $0.60 per additional million.7 A Worker that emits one trace per request and is otherwise idle isn't going to bankrupt you, but a fan-out pattern (one Worker invocation that does six subfetches generates seven spans) can multiply the bill quickly. Tune head_sampling_rate accordingly.

Step 6: Add the @sentry/cloudflare SDK for exception capture and richer context

The OTel export path is fast to set up and great for traces + structured logs, but it doesn't give you Sentry's full SDK feature set: breadcrumbs, release tracking, custom contexts, Sentry.captureException calls in your own code, and the ability to enrich events with tags and user identifiers. For that, install the SDK.

npm install @sentry/cloudflare@10.53.1

Update src/index.ts to wrap the handler with Sentry.withSentry:

// src/index.ts
import * as Sentry from "@sentry/cloudflare";

export interface Env {
  ENVIRONMENT: string;
  SENTRY_DSN: string;
  CF_VERSION_METADATA: { id: string; tag: string };
}

const handler: ExportedHandler<Env> = {
  async fetch(request, env): Promise<Response> {
    const url = new URL(request.url);
    const requestId = crypto.randomUUID();

    // Attach the request_id to every Sentry event for this invocation
    Sentry.setTag("request_id", requestId);

    if (url.pathname === "/slow") {
      // Wrap a real subrequest, not setTimeout — performance.now() only
      // advances after I/O in the Workers runtime, so a setTimeout-only
      // span would report 0ms in production.
      return await Sentry.startSpan(
        { op: "http.client", name: "fetch cloudflare trace" },
        async () => {
          const upstream = await fetch("https://www.cloudflare.com/cdn-cgi/trace");
          const body = await upstream.text();
          return new Response(body, { headers: { "content-type": "text/plain" } });
        },
      );
    }

    if (url.pathname === "/boom") {
      throw new Error("Intentional /boom error for observability testing");
    }

    return new Response(`hello from ${env.ENVIRONMENT}\n`);
  },
};

export default Sentry.withSentry(
  (env) => ({
    dsn: env.SENTRY_DSN,
    environment: env.ENVIRONMENT,
    // 10% tracing in prod, 100% in staging — adjust per environment via vars
    tracesSampleRate: env.ENVIRONMENT === "production" ? 0.1 : 1.0,
    enableLogs: true,
    sendDefaultPii: true,
  }),
  handler,
);

Add the matching wrangler bits:

# wrangler.toml — additions
# Required by @sentry/cloudflare for AsyncLocalStorage
compatibility_flags = ["nodejs_compat"]

# Auto-detect the release from the deployed Worker version
[version_metadata]
binding = "CF_VERSION_METADATA"

# Source maps for readable stack traces in Sentry
upload_source_maps = true

Then store the DSN as a secret (never as a [vars] entry, even though the DSN's public key is technically not a secret — keeping secrets uniform is a habit worth building):

npx wrangler secret put SENTRY_DSN -e production
# Paste your Sentry project's DSN when prompted

Deploy and trigger the /boom route. After a short delay — Sentry's docs note it takes "a couple of moments" for events to surface — the Issues view shows the exception with a full stack trace, the request_id you tagged, the release ID auto-detected from CF_VERSION_METADATA.id (SDK ≥10.35.0 reads it without manual extraction),6 and any breadcrumbs the SDK collected along the way.

Two known limitations to bake into your team's expectations:

  • Span durations of 0ms for CPU-only work — and setTimeout counts as CPU. In the Cloudflare Workers runtime, performance.now() and Date.now() only advance after I/O, as a mitigation against timing-side-channel attacks.17 A span that wraps only synchronous math, or a setTimeout/scheduler.wait with no other I/O, will show 0ms in production (it still ticks normally in wrangler dev). Instrument real I/O boundaries — fetch, KV get, R2 put, D1 query — and durations populate.
  • Don't double-pay for telemetry. If you wired up the OTel export in Step 5 and the SDK here, you'll get the same trace twice in Sentry. Pick one path. The SDK is the right choice when you want Sentry's API surface; the OTel pipeline is the right choice when you want telemetry forwarding with zero code in your Worker.

Step 7: Size the bill before you ship

The single most underrated step in production observability is doing the math up front. Numbers for a Worker that serves 15 million requests per month, on the Workers Paid plan ($5/month base9):

SettingLogs emittedLogs billedSpans emittedSpans billed7Monthly observability cost
head_sampling_rate = 1, 1 log + 1 invocation log per request, no traces30M(30M − 20M) × $0.60/M = $6.0000$6.00
head_sampling_rate = 1, structured logs, traces on, 1 span per request30M$6.0015M(15M is under the bundle alone, but logs already used the bundle) → 15M × $0.60/M = $9.00$15.00
head_sampling_rate = 0.1, 1 log + 1 invocation log per sampled request, 1 span per sampled request3M0 (within bundle)1.5M0 (within bundle)$0.00
Same as row 3 plus Logpush to S3 for the sampled events3M ingested, 3M delivered01.5M0+ $0.05/M × 3M = $0.15 delivery

Three lessons hide in that table. First, the 20M Paid bundle is shared across logs and spans starting March 2026 — they meter to the same quota.7 Second, head_sampling_rate is the single biggest lever you control: dropping from 100% to 10% on a 15M-request Worker collapses the entire observability bill from $15 to $0 (excluding the base $5/month Paid charge). Third, Logpush is additive delivery cost on top of Workers Logs ingest — if your destination is Sentry via OTel, you skip the Logpush line entirely.

The pragmatic production default for most teams is head_sampling_rate = 0.1 to 0.25 in production. The catch worth understanding before you ship: head sampling decides per invocation, before the handler runs, so a sampled-out request that later throws an exception loses its logs along with everything else. Cloudflare still records aggregate metrics (request count, error rate, P95 latency) at full fidelity in the Metrics Dashboard,18 so you'll see that errors happened — you just can't drill into individual failing requests on the dropped 90%.

If you need every error retained regardless of sampling, the documented escape hatch is to set up a Tail Worker on the Paid plan. The Cloudflare docs describe Tail Workers as fired after every producer invocation, distinct from the Workers-Logs head-sampling decision, so you can forward exception events to a second store without bloating your primary Workers Logs ingest.13 Inside your main handler, log the error structurally so the Tail Worker has rich context to work with:

// Always emit a structured exception log; head sampling decides whether
// it lands in Workers Logs, but a Tail Worker (Paid plan) will see it.
try {
  return await handleRequest(request, env);
} catch (err) {
  console.error({
    msg: "request_failed",
    request_id: requestId,
    err_name: (err as Error).name,
    err_message: (err as Error).message,
  });
  throw err;
}

Verification

After deploying with the full configuration above, run this sanity sweep:

# 1. Confirm Workers Logs is on
npx wrangler deployments list -e production
# The latest deployment's "observability" field should be enabled.

# 2. Generate a known test event
curl -is https://nlt-observability-demo-prod.<your-subdomain>.workers.dev/boom \
  | head -1
# Expected: HTTP/2 500

# 3. Tail it
npx wrangler tail -e production --status error --search "user triggered boom"
# Wait <60s; you should see the structured error event you logged.

# 4. Cross-check Sentry
# Open Sentry → Issues. You should see one new issue with the request_id
# you used. Open it; the Trace tab should show one span tree.

# 5. Check log ingest is metered correctly
# Cloudflare Dashboard → Workers & Pages → Observability → Workers Logs.
# The "Events ingested" counter should match your request count
# multiplied by your `head_sampling_rate` and by (1 + custom_logs_per_request).

If steps 1–3 work but the Sentry trace tab is empty after five minutes, the most common cause is the x-sentry-auth header value on the Cloudflare destination — re-copy it from Sentry's "OpenTelemetry (OTLP)" panel rather than typing it.

Troubleshooting

"Error: Wrangler requires at least Node.js v20.0.0." Update Node — Wrangler 4.16 and later require Node 20+, even though the v4 minimum was originally documented as Node 18.19 Install Node 22 LTS (Maintenance) or, preferably, Node 24 LTS (Active LTS through October 2026).

AsyncLocalStorage is not defined after wiring up @sentry/cloudflare. Add compatibility_flags = ["nodejs_compat"] to wrangler.toml and redeploy. The Sentry SDK uses Node's AsyncLocalStorage to scope context across async hops, and the Workers runtime exposes it only when this flag is set.6

Sentry traces show all spans with 0ms duration. Expected for CPU-bound code in the Workers runtime. performance.now() and Date.now() advance only after an I/O boundary, so spans that wrap pure compute — including a setTimeout or scheduler.wait with no other I/O — report zero. Wrap real I/O calls (fetch, KV get, R2 put, D1 query) in Sentry.startSpan and durations will populate.17

Workers Logs UI shows no logs even though wrangler tail does. Two likely causes. First, the Wrangler config's [observability] block didn't deploy — confirm with npx wrangler deployments list that the most recent deploy carries observability: enabled. Second, you crossed the 5-billion-per-day per-account ceiling and Cloudflare flipped a forced 1% head sample for the rest of the UTC day.3 Both clear at midnight UTC.

Tail enters sampling mode immediately on a busy Worker. Filter aggressively (--status error, --search "...") — the filter reduces volume server-side before sampling kicks in. Remember filters take up to 60 seconds to take effect after a change.12 For sustained debugging on a high-traffic Worker, either send a copy of logs to a Tail Worker (paid) or use the Workers Logs dashboard's Query Builder, which works on the full ingested stream rather than the sampled tail stream.18

Next steps

You now have a Worker that emits structured logs, samples invocations cleanly, surfaces real-time errors in your terminal, ships traces and logs to Sentry over OTLP, and captures exceptions with the Sentry SDK. Where to go from here:

  • Wire the same OTel destination pattern to Honeycomb, Axiom, Grafana Cloud, or PostHog — the Worker-side config is identical; only the dashboard destination changes.
  • Pair this setup with the Cloudflare Workers + R2 image-CDN tutorial — that Worker is a great candidate for sampled tracing since the cache-hit path is hot and the transform path is the one you actually need to debug.
  • If your Worker fronts a Postgres database, the Production Postgres pooling tutorial with PgBouncer + Supavisor covers tagging connections with application_name — the same idea, applied to your DB tier, so a single request identifier can trace through Worker → pool → backend.
  • For agent-driven alerting on top of your Sentry data, Cloudflare's Workers Observability MCP server (remote endpoint https://observability.mcp.cloudflare.com) lets MCP-aware clients query your Workers Logs and metrics in natural language — useful for on-call triage.20

Footnotes

Footnotes

  1. Wrangler version verified against the npm registry on May 17, 2026 via npm view wrangler version4.92.0. The npm dist-tags for wrangler also expose legacy: 3.114.17 for users still on v3.

  2. @sentry/cloudflare version verified via npm view @sentry/cloudflare version10.53.1, May 17, 2026. 2

  3. Cloudflare official documentation, "Workers Logs", last updated 2026-02-06. https://developers.cloudflare.com/workers/observability/logs/workers-logs/ 2 3 4 5 6 7

  4. Cloudflare official documentation, "Real-time logs". https://developers.cloudflare.com/workers/observability/logs/real-time-logs/ 2

  5. Cloudflare official documentation, "Export to Sentry", last updated 2026-04-23. https://developers.cloudflare.com/workers/observability/exporting-opentelemetry-data/sentry/ 2 3 4 5 6

  6. Sentry official documentation, "Cloudflare". https://docs.sentry.io/platforms/javascript/guides/cloudflare/ 2 3 4 5

  7. Cloudflare official documentation, "Traces" — billing begins March 1, 2026; each span is one observability event, same per-million quota as logs. https://developers.cloudflare.com/workers/observability/traces/ 2 3 4 5 6

  8. Cloudflare official documentation, "Install/Update Wrangler". Wrangler supports the Current, Active, and Maintenance LTS versions of Node.js. https://developers.cloudflare.com/workers/wrangler/install-and-update/

  9. Cloudflare official documentation, "Pricing". Workers Paid is $5 USD/month minimum, includes 10M requests + 30M CPU-ms; overage at $0.30/M requests, $0.02/M CPU-ms. https://developers.cloudflare.com/workers/platform/pricing/ 2

  10. Sentry official documentation, "OpenTelemetry Protocol (OTLP)" — OTLP support for traces and logs is currently in open beta. https://docs.sentry.io/concepts/otlp/ . Sentry Developer (free) plan limits per Sentry's pricing page: https://sentry.io/pricing/ 2

  11. Cloudflare official documentation, "Wrangler commands". The wrangler tail --status filter accepts ok, error, or canceled, mirroring the invocation outcome enum recorded by the Workers runtime. https://developers.cloudflare.com/workers/wrangler/commands/

  12. Cloudflare official documentation, wrangler tail filter behavior — up to 60 seconds for filter changes to take effect server-side. https://developers.cloudflare.com/workers/wrangler/commands/ 2

  13. Cloudflare official documentation, "Tail Workers" — available to Workers Paid and Enterprise customers. https://developers.cloudflare.com/workers/observability/logs/tail-workers/ 2

  14. Cloudflare blog, "Send Cloudflare Workers logs to a destination of your choice with Workers Trace Events Logpush" — Workers Logpush is on Workers Paid, priced at $0.05 per million delivered events. https://blog.cloudflare.com/workers-logpush-ga/ 2

  15. Cloudflare documentation cross-reference: Tail Workers are billed by CPU time. https://developers.cloudflare.com/workers/observability/logs/tail-workers/

  16. Cloudflare changelog, "New Best Practices guide for Workers" (2026-02-15) — best practice recommends enabling Workers Logs and Traces and configuring observability before deploying to production. https://developers.cloudflare.com/changelog/post/2026-02-15-workers-best-practices/

  17. Cloudflare official documentation, "Performance and timers" — performance.now() and Date.now() advance only on I/O boundaries as a timing-attack mitigation. https://developers.cloudflare.com/workers/runtime-apis/performance/ 2

  18. Cloudflare official documentation, "Metrics and analytics" — aggregate metrics (request count, success rate, error rate, CPU time, duration, response size) are recorded for every invocation independent of head_sampling_rate. https://developers.cloudflare.com/workers/observability/metrics-and-analytics/ See also "Query Builder" for filtering ingested logs: https://developers.cloudflare.com/workers/observability/query-builder/ 2

  19. Cloudflare workers-sdk issue tracker — newer Wrangler v4 minors bumped the practical Node requirement to v20. https://github.com/cloudflare/workers-sdk/issues/9352

  20. Cloudflare official MCP server repository, "workers-observability" — remote endpoint at https://observability.mcp.cloudflare.com. https://github.com/cloudflare/mcp-server-cloudflare/tree/main/apps/workers-observability


FREE WEEKLY NEWSLETTER

Stay on the Nerd Track

One email per week — courses, deep dives, tools, and AI experiments.

No spam. Unsubscribe anytime.