Crawler logs

Ingest not arriving

Work through the causes of a crawler log drain that stays on awaiting first logs.

Settings → Crawler logs still reads awaiting first logs after you deployed. Work through these in order — the first two catch most cases.

1. Test the endpoint and token directly

This separates configuration from your code entirely, and you can run it from anywhere. ?validate=1 parses your sample and reports on it without writing anything.

curl -X POST "YOUR_ENDPOINT?validate=1" \
  -H "X-Shruwd-Ingest-Token: YOUR_TOKEN" \
  -H "Content-Type: application/x-ndjson" \
  -d '{"timestamp":"2026-01-01T00:00:00Z","host":"example.com","path":"/","user_agent":"GPTBot","status":200,"client_ip":"1.2.3.4"}'
ResponseMeans
usable: trueEndpoint and token are both fine — the problem is in your app or platform. Go to step 2
401Wrong or revoked token. See below
404Wrong brand in the endpoint URL
422The sample can't be used. reason says why

Creating a token revokes the previous one. If you minted a new token in Settings after setting up the drain, the one your app is using is dead. Only the newest token works.

2. On Vercel, check the drain

In Team Settings → Drains:

  • The drain isn't marked as errored or paused.
  • Its sources include static, lambda, edge, external, redirect and firewall.
  • Its environments include production, and it has no sampling rule.

Then check Team Settings → Security & Privacy → IP Address Visibility is on. Hidden IPs look like a working drain: logs arrive and the card reads active, but every line is refused, so the Crawlers page stays empty.

3. Check the variables in the deployed environment

Set locally and forgotten in the host's configuration is the single most common cause. Check the values in the environment your site actually runs in, not your local .env.

If you are using server middleware, it logs one line the first time a send fails — look for [shruwd] log drain not working: in your app's logs. That line names the cause.

If you switched the middleware to useRuntimeConfig(), confirm both halves: the keys declared in nuxt.config.ts and the NUXT_ prefix on the variables. Missing either reads undefined silently.

4. Check whether the request was served from cache

This only applies to server middleware. A request your CDN answers never reaches your origin, so the middleware never sees it.

Check cf-cache-status on the response:

ValueReached your origin?
DYNAMIC or MISSYes
HITNo — the middleware could not have seen it

If most of your pages are cached, middleware is the wrong path. Switch to the forwarder Worker or a platform drain.

cf-cache-status: DYNAMIC means Cloudflare did not serve it from Cloudflare's cache. It does not guarantee the request reached your application — a second CDN behind it can still answer from static files.

5. Wait a little

Middleware batches: it sends at 20 events or 10 seconds, whichever comes first. One test request will not appear immediately.

Crawler traffic is also genuinely intermittent. A low-traffic site may wait hours between real AI-crawler hits.

Other things worth knowing

Logs arrive but the card shows gaps. Days with data on the Settings card shows how many of the last 30 days received anything. Gaps switch off the diagnostic that needs 30 uninterrupted days.

Logs arrive but nothing is counted. Check the Unverified column on the Crawlers page. Hits from IPs outside the vendor's published ranges are shown but never counted — which is correct, and still proves your pipe works.

It stopped partway through the month. Check Lines this month against your plan's allowance. Past the cap, lines are discarded until the month turns. The Crawlers page says so rather than showing a flat chart.

A 413 response. Your batches are over the size cap. On Cloudflare Logpush, set max_upload_bytes to 5000000.

Next steps