You’re not actually monitoring Cloudflare Workers until you’re looking at the logs and traces that Workers Tail gives you, not just the basic analytics.

Let’s see a Worker in action. Imagine a simple Worker that adds a custom header to every request.

// index.js
export default {
  async fetch(request) {
    const url = new URL(request.url);
    let response = await fetch(request); // Pass through to origin

    // Clone the response so we can mutate it
    response = new Response(response.body, response);

    // Add the custom header
    response.headers.set("X-Custom-Worker-Header", "Hello from Cloudflare Workers!");

    return response;
  },
}

When this Worker is deployed and receives a request, here’s what you’d see in your browser or with curl:

$ curl -I https://my-worker.example.com/some/path
HTTP/2 200
date: Tue, 01 Aug 2023 10:00:00 GMT
server: cloudflare
cf-ray: 7f1a3b2c1d0e4f5a-LAX
content-type: text/html;charset=UTF-8
cache-control: public, max-age=0, no-cache
vary: Accept-Encoding
x-custom-worker-header: Hello from Cloudflare Workers!
cf-cache-status: MISS
report-to: {"endpoints":[{"url":"https://a.next.gdn/cdn-cgi/beacon/comet"}],"group":"cf-nel","max_age":604800}
nel: {"success_fraction":0,"report_to":"cf-nel"}
alt-svc: h3=":443"; ma=86400

Notice the x-custom-worker-header. That’s your Worker doing its thing. But how do you know when it’s doing it, or if it’s failing? That’s where observability comes in.

Cloudflare Workers are designed to be highly scalable and distributed. They run on Cloudflare’s edge network, meaning your code executes in data centers geographically close to your users. This proximity reduces latency. The core problem Workers solve is enabling you to run custom logic at the edge, right in front of your origin servers, without managing infrastructure. You can intercept requests, modify responses, serve content directly, or even build entirely new APIs.

The fetch event is the entry point for every Worker. When a request hits a route matched by your Worker, Cloudflare invokes this fetch handler. Inside fetch, you receive a Request object and must return a Response object. You can then use the fetch API within your Worker to make outgoing HTTP requests, whether to your origin server, other APIs, or even other Workers. This allows for complex request routing, authentication, A/B testing, and dynamic content generation.

The most surprising true thing about Workers Tail is that it’s not just for debugging; it’s your primary window into the real-time behavior of your edge applications. While basic analytics show request counts and response times, Tail shows you the individual requests and the exact code execution paths that generated them. It’s like having a debugger attached to every single invocation of your Worker, across the entire global network.

Let’s enable Tail for our example Worker. You’d go to your Worker’s dashboard, navigate to "Logs," and then enable "Live Tail."

Once enabled, you can use the wrangler tail command-line tool to stream these logs to your local terminal.

wrangler tail --format=json

This command will start streaming JSON-formatted logs. If our Worker received a request, you might see output like this:

{
  "event": "log",
  "event_timestamp": "2023-08-01T10:00:05.123Z",
  "request": {
    "url": "https://my-worker.example.com/some/path",
    "method": "GET",
    "headers": {
      "accept": "*/*",
      "user-agent": "curl/7.81.0"
    }
  },
  "response": {
    "status": 200,
    "headers": {
      "content-type": "text/html;charset=UTF-8",
      "x-custom-worker-header": "Hello from Cloudflare Workers!",
      "cf-cache-status": "MISS"
      // ... other headers
    }
  },
  "outcome": "ok",
  "duration_ms": 50,
  "logs": [
    {
      "level": "log",
      "message": "Processing request for /some/path",
      "timestamp": "2023-08-01T10:00:05.120Z"
    }
  ]
}

This JSON payload is incredibly rich. It contains the incoming request details, the outgoing response details (including headers after your Worker modified them), the outcome (whether it succeeded or failed), the total duration_ms for the Worker’s execution, and any explicit logs you’ve added using console.log() within your Worker code.

To get this detailed trace, your Worker needs to be configured to send logs. By default, console.log statements in Workers are captured by the platform. The wrangler tail command subscribes to this stream. For more advanced tracing, you can integrate with external observability platforms like Datadog or Honeycomb by sending custom events from your Worker using libraries or direct HTTP requests to their APIs.

The wrangler tail command itself is a powerful tool. You can filter logs based on keywords, request URLs, or even the outcome. For instance, to see only errors:

wrangler tail --filter='outcome == "error"'

Or to see logs from a specific URL:

wrangler tail --filter='request.url ~ "specific_path"'

The ~ operator indicates a regular expression match. This filtering is done client-side by wrangler, but Cloudflare’s internal logging infrastructure is what makes the data available for streaming.

The true magic of Workers Tail isn’t just seeing logs; it’s seeing the distributed trace. When your Worker makes an outgoing fetch request to your origin, that request is often tagged with a CF-Trace-Id header. If your origin is also configured to log or propagate this header, you can correlate the Worker’s execution with your origin’s processing for that specific request. This is crucial for debugging issues that span both your edge logic and your backend.

The next concept you’ll want to explore is how to ingest these logs into a more permanent storage or analysis system, rather than just watching them stream.

Want structured learning?

Take the full Cloudflare course →