How to Collect Metrics from Cloudflare Workers (2026 Guide)
Why Metrics Collection in Workers Is Different
Traditional servers run long-lived processes. You install an agent, it scrapes /metrics every 15 seconds, and life is simple. Cloudflare Workers don’t work that way.
Workers run on V8 isolates. There is no persistent process. There is no process.beforeExit, no SIGTERM, no reliable lifecycle hook that fires before the runtime shuts down. The isolate freezes between invocations, which means timer-based flushing (like setInterval) is fundamentally unreliable. If you try to batch metrics and flush them on a timer, the isolate may freeze before the flush completes, and your data disappears.
This single constraint shapes every decision about how to collect metrics from Cloudflare Workers. Every method described below either works within this constraint or provides a workaround for it.
Understanding telemetry, MELT, and OpenTelemetry in the serverless context helps frame the three telemetry layers that Cloudflare provides: platform metrics (automatic), structured logs (semi-automatic), and distributed traces (configurable). Each layer serves a different observability need, and no single layer covers everything.
Looking for a fast path? If you want custom counters and histograms on Workers without running agents or configuring bindings, Distlang Metrics offers an API-first approach built for serverless.
Platform-Level Metrics (Zero Code Required)
Workers Metrics Dashboard
Every deployed Worker automatically collects aggregate metrics: request count, error rate, CPU time, wall time, subrequest count, and memory usage. These appear in the Cloudflare dashboard under the Workers Analytics tab, and you can inspect them for up to three months in maximum increments of one week.
This is your free baseline. You get it without writing a single line of instrumentation code. But platform metrics are aggregated and sampled, so they show trends rather than per-request detail. You can see that error rates spiked at 3pm, but you can’t see which specific requests failed or why.
Each Worker isolate has a 128 MB memory limit, and the dashboard surfaces memory usage alongside CPU and wall time. For most Workers handling API requests or running lightweight logic, memory is rarely the bottleneck. CPU time and wall time tell a more useful story.
CPU Time vs. Wall Time
This distinction matters more than most developers realize. CPU time measures how long the V8 engine spent executing your JavaScript, excluding I/O wait. Wall time measures total elapsed time from invocation start to completion, including all I/O waits.
A Worker with 5ms CPU time but 200ms wall time is spending 195ms waiting on external calls (database queries, API fetches, KV reads). If you’re trying to optimize performance, the gap between CPU time and wall time tells you where to look.
There’s a critical caveat here. The Worker runtime does not expose accurate timing information for CPU-bound work, as a protection against Spectre-class side-channel attacks. The clock only updates on I/O boundaries. So if you’re doing heavy computation without any I/O interleaving, your custom duration measurements will read as 0ms. For a deeper look at metric types and best practices, including how to work around this limitation, see our guide on the topic.
GraphQL Analytics API
The same platform metrics visible in the dashboard are accessible programmatically through Cloudflare’s GraphQL Analytics API. This lets you query specific quantiles (cpuTimeP50, cpuTimeP99, wallTime distributions) and build custom dashboards or alerts outside the Cloudflare UI.
The GraphQL API is useful when you want to pull Workers metrics into an existing monitoring tool or create automated reports. But it’s still limited to the same aggregated, sampled data that the dashboard shows.
Workers Observability and Query Builder
Cloudflare launched Workers Observability (with a Query Builder in beta) in December 2025. This tool lets you run structured queries across logs from all Workers in your account, extract metrics from those logs, and create graphical or tabular visualizations.
The Query Builder bridges the gap between raw logs and metrics. Instead of staring at log lines, you can write queries that count error occurrences by status code, calculate average response times per endpoint, or track usage patterns per customer. It turns logs into the kind of aggregated view that platform metrics provide, but with the flexibility to slice data across any dimension you’ve logged.
The Core Primitive: ctx.waitUntil()
Before diving into custom metrics, you need to understand the one mechanism that makes all async telemetry collection possible in Workers.
ctx.waitUntil() accepts a Promise and keeps the isolate alive until that Promise resolves, without blocking the client’s response. The user sees their response immediately. The telemetry flush finishes in the background, within the Worker’s resource budget.
export default {
async fetch(request, env, ctx) {
const response = doWork(request);
// Flush metrics after the response, non-blocking
ctx.waitUntil(sendMetrics(env));
return response;
}
};
Resource constraints apply. Workers get up to 30 seconds of wall-clock time and between 50ms and 30 seconds of CPU time depending on your plan. The work done inside waitUntil counts against those budgets. A slow flush still consumes resources.
As of August 2025, Workers support importing waitUntil directly from cloudflare:workers without requiring the request context object. This is useful in middleware or utility functions where you don’t have access to ctx. For implementation details around waitUntil flush semantics, including edge cases and failure modes, we have a dedicated deep dive.
The PostHog team’s approach illustrates the flush problem clearly. Their Cloudflare Workers integration sets flushAt to 1 and flushInterval to 0, sending captured data immediately without batching. Batched data sent asynchronously risks loss because the Worker can terminate before the flush completes. The FlareLog SDK takes the same aggressive approach, automatically applying batchSize: 1 and flushIntervalMs: 0 for worker-optimized settings.
This is the opposite of what you’d do on a long-lived server, where batching improves efficiency. On Workers, batching risks data loss. When configuring buffering and flush behavior, favor immediate sends wrapped in waitUntil over delayed batches.
Custom Metrics
Platform metrics tell you about your Worker’s health. Custom metrics tell you about your application’s behavior. How many signups happened this hour? What’s the P95 latency of your upstream API? How many items did users add to their carts?
Workers Analytics Engine
Cloudflare’s Analytics Engine is purpose-built for high-cardinality custom metrics at scale. You write data points from Worker code, then query them via a SQL API.
Each data point consists of three components: blobs (strings used for grouping and filtering, sometimes called labels), doubles (numeric values you want to record), and indexes (strings used as sampling keys). Writes are non-blocking and don’t impact request latency. Datasets are created automatically the first time you write to them after defining a binding in your Wrangler config.
Pricing starts with 10 million writes per month free, then $0.25 per million after that.
The main advantage over traditional metrics systems like Prometheus is cardinality handling. In Prometheus, every new label combination creates a new time series under the hood. If you have multiple labels per data point, the number of series can explode. Analytics Engine was designed to avoid this problem entirely.
The main friction point is the schema. Columns are named blob1, blob2, double1, double2 rather than descriptive names like endpoint or latency_ms. You need to maintain your own mapping between column positions and their meanings, which gets confusing as your data model grows.
API-First Metrics Services
For teams that want custom counters and histograms without Analytics Engine’s blob/double schema or the overhead of running Prometheus, API-first metrics services offer a lighter path. You define metrics in code, record values during request handling, and flush via HTTP using ctx.waitUntil(). No bindings, no Wrangler config changes, no infrastructure to manage.
Distlang Metrics provides a Cloudflare Workers quickstart for exactly this pattern. You get a hosted dashboard per metric set with AI-suggested chart titles, and the free tier includes 500k rows per month.
This approach works well for solo developers and small teams who want visibility into application behavior without committing to a full observability platform. The tradeoff is that you’re sending data to an external service, so you depend on that service’s availability and retention policies.
The cloudflare-worker-metrics npm Package
This community package takes an interesting hybrid approach. Metrics are aggregated during invocation using typed counters and histograms, then flushed via console.log. A companion tool, cloudflare-worker-metrics-exporter, reads those logs and emits them to an OTEL endpoint.
It’s clever because it avoids making external HTTP calls from within the Worker itself. The downside is the two-step pipeline: you need the exporter running somewhere to complete the chain.
Logs as a Metrics Source
Workers Logs and Structured Logging
Workers Logs capture detailed invocation data automatically. On the Free plan, you get 200,000 log events per day with 3-day retention. The Paid plan extends retention to 7 days.
The key to making logs useful for metrics extraction is structured logging. When you log JSON objects via console.log, Workers Logs automatically extracts fields and indexes them. This means you can later query across any dimension with unlimited cardinality.
console.log(JSON.stringify({
endpoint: "/api/users",
status: 200,
latency_ms: 45,
customer_id: "cust_abc123"
}));
This structured approach lets the Query Builder treat your logs as a queryable metrics source. You can count errors by endpoint, compute average latency by customer, or track any custom dimension you include in your log output.
Tail Workers
A Tail Worker is a special Worker that’s automatically invoked after a “producer” Worker finishes executing. It receives execution events from the producer, including details from throughout the request lifecycle and any sub-requests.
Tail Workers are powerful because they let you filter, transform, and forward telemetry data to any HTTP endpoint without adding any code to your producer Worker. They’re available on Workers Paid and Enterprise tiers.
A common pattern is using Tail Workers to capture 100% of error events while sampling normal requests at a lower rate. This gives you complete visibility into failures without the cost of storing every successful invocation.
head_sampling_rate
At high request volumes, capturing 100% of invocations gets expensive fast. The head_sampling_rate setting in your Wrangler configuration controls what percentage of invocations emit logs and traces.
Cloudflare’s own best-practice guidance is direct: enable logs and traces before you deploy to production, because when an intermittent error appears, you need data already being collected. Use head_sampling_rate to control volume and manage costs, and pair it with Tail Workers to ensure errors are always captured regardless of sampling.
Export and Integration
OpenTelemetry Export
Cloudflare Workers support exporting OTel-compliant telemetry data to any destination with an OTLP endpoint. Supported destinations include Honeycomb, Grafana Cloud, Axiom, and Sentry.
Here’s the critical limitation that most articles fail to mention: OTel export currently supports traces and logs only. Metrics export is not yet supported. Cloudflare has stated they’re actively working on adding metrics support, but as of now, if you need to export custom metrics to an external platform via OTel, you can’t do it through Cloudflare’s native export pipeline.
With automatic instrumentation (in open beta since February 2026), Cloudflare captures telemetry for fetch calls, binding operations (KV, R2, Durable Objects), and handler invocations with no code changes required. This is genuinely useful for understanding request flows. Just don’t expect it to cover custom business metrics.
For background on the broader MELT framework and how OpenTelemetry fits into serverless architectures, see our overview.
@microlabs/otel-cf-workers
Because Workers use V8 directly (not Node.js), you can’t drop in the standard @opentelemetry/sdk-node package. The @microlabs/otel-cf-workers community library fills this gap, providing Workers-compatible OTel instrumentation that auto-instruments fetch calls, cache operations, and binding access.
Practitioners on forums report that this library is the go-to choice for teams that want manual OTel instrumentation beyond what Cloudflare’s automatic tracing provides. It handles the V8-specific constraints that would otherwise require significant boilerplate.
Workers Logpush
Logpush exports Workers Trace Event Logs to long-term storage or external logging platforms. Supported destinations include Cloudflare R2, AWS S3, Google Cloud Storage, Azure Blob Storage, Datadog, Splunk, Sumo Logic, and Elastic.
Pricing is $0.05 per million requests delivered to your destination, with 10 million requests per month included free. For teams that already have a centralized logging platform, Logpush is often the easiest integration path. It doesn’t require any code changes to your Workers.
Cloudflare Prometheus Exporter
Cloudflare provides an official Prometheus exporter that runs as a Worker with Durable Objects. It scrapes the GraphQL Analytics API and exposes 90+ Prometheus metrics covering requests, bandwidth, threats, workers, load balancers, SSL certificates, and more.
The Durable Objects component handles stateful counter accumulation and background refresh. This exporter is available to all plan tiers (Free, Pro, Business, Enterprise) and is a good fit for teams that already run Prometheus and Grafana and want to pull Cloudflare data into their existing stack.
Decision Framework: Which Method for Which Team
Picking the right metrics approach depends on your team size, traffic volume, existing tooling, and budget. Here’s how to think about it.
Solo developer or MVP stage. Start with platform metrics (free, automatic) plus structured logging via console.log with JSON. If you need custom business metrics without infrastructure setup, an API-first metrics service gets you to a dashboard in minutes. For guidance on assembling a lightweight monitoring setup at this stage, we have a dedicated walkthrough.
Small team, Workers as primary infrastructure. Workers Logs plus Logpush to R2 covers most use cases at minimal cost with zero external vendor dependencies. Add Analytics Engine for custom metrics that need high-cardinality queries (per-customer usage, per-endpoint latency). This combination keeps everything within Cloudflare’s ecosystem.
Team with existing observability platform. Use OTel export for traces and logs to your existing backend (Datadog, Honeycomb, Grafana Cloud). Use Logpush for long-term log storage. Accept that OTel metrics export isn’t available yet and fill the gap with Analytics Engine or an external metrics API. Datadog works well for heterogeneous multi-cloud environments. Honeycomb is stronger when your Workers generate high-cardinality request patterns and you need to query arbitrary dimensions quickly.
Platform team at scale. Deploy the Prometheus exporter for infrastructure-level metrics, use Tail Workers for 100% error capture with sampled normal traffic, enable automatic OTel tracing, and use Analytics Engine or a dedicated metrics service for business-level instrumentation. Set head_sampling_rate based on traffic volume.
| Method | Code Required | Cost | Best For |
|---|---|---|---|
| Platform metrics dashboard | None | Free | Quick health checks |
| GraphQL Analytics API | Query only | Free | Custom dashboards, automation |
| Workers Observability | Structured logs | Free (with limits) | Ad-hoc investigation |
| Analytics Engine | Binding + writes | 10M free, then $0.25/M | High-cardinality custom metrics |
| API-first metrics service | HTTP calls in waitUntil | Varies by provider | Fast setup, no bindings |
| Workers Logs | console.log (JSON) | Free tier: 200K/day | Debugging, log-derived metrics |
| Tail Workers | Separate Worker | Paid plan required | Error capture, forwarding |
| OTel Export | Config only (auto) or library | Free from CF, pay backend | Traces and logs to existing tools |
| Logpush | Config only | 10M free, then $0.05/M | Long-term storage, external platforms |
| Prometheus Exporter | Deploy exporter Worker | Free (all plans) | Teams running Prometheus/Grafana |
Common Pitfalls
Timer-based flushing doesn’t work. If you’re using setInterval or setTimeout to batch and flush metrics, stop. The isolate freezes between invocations and your timers won’t fire. Use ctx.waitUntil() with immediate sends instead. For an in-depth look at buffering and flush strategies for edge functions, including failure modes and recovery patterns, see our dedicated guide.
Spectre mitigations break CPU timing. Custom performance.now() calls for measuring CPU-bound work will return 0ms because the clock only advances on I/O boundaries. If you need to measure computation time, interleave a no-op I/O call (like reading from a cache) to force a clock update, though this adds its own overhead.
OTel metrics export is not available yet. This trips up teams who assume they can export everything via OTel. You can export traces and logs. Custom metrics and infrastructure metrics must go through other channels (Analytics Engine, Logpush, Prometheus exporter, or an external API).
Analytics Engine column naming. Columns are blob1 through blob20 and double1 through double20. There are no aliases at the schema level. Document your mappings somewhere your team can find them, or you’ll spend hours figuring out what double3 represents six months from now.
Batching causes data loss on short-lived handlers. The PostHog and FlareLog teams both learned this the hard way. On Workers, set batch size to 1 and flush interval to 0. Wrap every send in waitUntil. The slight inefficiency of per-event sends is worth it compared to losing data. For more on instrumenting counters and histograms specifically on Workers, see our implementation guide.
Forgetting to enable observability before production. Cloudflare’s own documentation says it plainly: production Workers without observability are a black box. Enable logs and traces in your Wrangler configuration before your first production deploy, not after the first incident.
FAQ
What metrics does Cloudflare collect automatically for Workers?
Cloudflare automatically collects request count, error rate, CPU time, wall time, subrequest count, and memory usage for every deployed Worker. These metrics are available in the dashboard and via the GraphQL Analytics API, with up to three months of history in one-week increments. No code changes are needed.
How does ctx.waitUntil() help with metrics collection?
ctx.waitUntil() keeps the Worker isolate alive after the response has been sent to the client, allowing background work like metrics flushing to complete. Without it, the isolate may freeze before your telemetry data reaches its destination. It’s the foundational mechanism for all non-blocking metrics collection in Workers.
Can I export OpenTelemetry metrics from Cloudflare Workers?
Not yet. Cloudflare’s OTel export currently supports traces and logs only. Metrics export (both infrastructure metrics and custom metrics) is not available through the native OTel pipeline. Cloudflare has said they’re working on it. In the meantime, use Analytics Engine, Logpush, or an external metrics API for custom metrics.
What is Workers Analytics Engine and when should I use it?
Analytics Engine is Cloudflare’s built-in custom metrics system designed for high-cardinality data. You write data points (blobs for labels, doubles for values) from your Worker code, then query via SQL API. Use it when you need per-customer, per-endpoint, or per-feature metrics that go beyond what platform metrics provide. It includes 10 million writes per month free.
How do Tail Workers differ from regular Workers Logs?
Workers Logs passively capture invocation data. Tail Workers are separate Workers that actively process execution events from a producer Worker after it completes. Tail Workers let you filter, transform, and forward data to any HTTP endpoint. A common pattern is using Tail Workers to capture 100% of errors while sampling normal traffic at a lower rate.
Why does my custom CPU timing measurement show 0ms?
The Worker runtime deliberately limits timing precision to protect against Spectre-class side-channel attacks. The clock only updates on I/O boundaries. If your code does pure computation without I/O, performance.now() calls before and after will return the same value. Interleave a small I/O operation to force a clock update if you need approximate timing.
What’s the cheapest way to collect metrics from Cloudflare Workers?
The free tier gives you platform metrics in the dashboard, 200,000 log events per day with 3-day retention, and 10 million Analytics Engine writes per month. Combined with structured JSON logging and the Query Builder, this covers basic observability needs for low-traffic Workers at zero cost.
Should I batch metrics sends in Cloudflare Workers?
No. Unlike long-lived servers where batching improves efficiency, Workers can terminate before batched data flushes. Set batch size to 1 and flush interval to 0, wrapping each send in ctx.waitUntil(). Multiple production libraries (PostHog, FlareLog) have independently arrived at this same pattern after experiencing data loss with batching.
Collecting metrics from Cloudflare Workers requires understanding the constraints of the runtime and picking the right tools for your scale. Start with platform metrics and structured logging, add custom instrumentation as your needs grow, and always wrap async telemetry work in ctx.waitUntil().
If you want custom counters and histograms without configuring bindings or managing infrastructure, get started with Distlang Metrics and go from code to dashboard in minutes.