简介
依赖服务响应缓慢时,支持 API 会返回难以判断的异常。你将重现这一现象,并通过请求 ID 将其与日志关联起来;然后修复处理程序,使调用方收到有明确含义且受时间限制的失败响应,同时确保正常请求仍能成功。最后,你还会验证真实的云端响应和独立的实时日志流。
请在这台全新的虚拟机中使用你自己的学习账号开始实验。前置要求包括常规的 Wrangler 部署、服务绑定和本地测试;实验不会复用之前的 Worker 或虚拟机。初始化过程会安装 Node.js 22.22.0、Wrangler 4.131.1 和 Miniflare 4.20260730.0,并提供有问题的调用方和用于模拟的上游服务。上游服务可以返回模拟数据、受控的 503 响应,或延迟 2.5 秒。实验不需要数据库、已购买的域名或高负载测试。
运行时异常、主动返回的 HTTP 504,以及执行限制导致的失败,是三种不同的现象。你将分别检查这些证据,不能把所有 5xx 响应都视为平台故障。
重现超时并建立关联
在本步骤中,你将在本地重现依赖服务响应缓慢导致的异常。阅读调用方代码和提供的上游服务代码。调用方设置了 400 毫秒的截止时间,但没有捕获被拒绝的 fetch;上游服务的 slow 模式会等待 2.5 秒。
cd /home/labex/project/failure-diagnostics
cat src/index.js
cat upstream/index.js
生成唯一的资源名称。第一个未加引号的 EOF 会将该变量展开到两个配置文件中。UPSTREAM 服务绑定会让测试装置保持私有;请求 URL 中的主机名不会选择公共服务。
WORKER_NAME="labex-diagnose-$(node -p "require('node:crypto').randomBytes(6).toString('hex')")"
cat > wrangler.jsonc <<EOF
{
"name": "$WORKER_NAME",
"main": "src/index.js",
"compatibility_date": "2026-07-30",
"workers_dev": true,
"preview_urls": false,
"services": [{"binding": "UPSTREAM", "service": "$WORKER_NAME-upstream"}]
}
EOF
cat > upstream/wrangler.jsonc <<EOF
{
"name": "$WORKER_NAME-upstream",
"main": "index.js",
"compatibility_date": "2026-07-30",
"workers_dev": false,
"preview_urls": false
}
EOF
在一个本地开发进程中同时运行这两个配置。后台任务会让终端保持可用;> 和 2>&1 会将输出和错误写入 dev.log。等待日志显示 Ready 后再发送请求。
npx wrangler dev -c wrangler.jsonc -c upstream/wrangler.jsonc --ip 0.0.0.0 --port 8080 > dev.log 2>&1 &
cat dev.log
使用 -H 附加一个简短的模拟请求 ID。处理程序只接受符合限制格式的 ID,否则会自动生成一个 ID。--max-time 限制的是 curl 客户端的等待时间,与处理程序自身的截止时间不同。
curl -i --max-time 6 -H "X-Request-ID: healthy-one" "http://127.0.0.1:8080/api/check?mode=healthy"
curl -i --max-time 6 -H "X-Request-ID: slow-one" "http://127.0.0.1:8080/api/check?mode=slow"
cat dev.log
正常请求应返回包含模拟上游数据的 200 响应。slow 模式应返回本地 500 错误,并在日志中显示:先出现针对 slow-one 的 request_started 记录,随后出现未捕获的超时异常。具体的本地错误页面和堆栈信息可能不同。这说明截止时间会导致请求被拒绝,但不能说明系统已经提供了有用的错误响应。修改调用方之前,先运行验证。
修复失败响应和诊断信息
在本步骤中,捕获有时间限制的上游故障,同时确保诊断信息有用,并且不记录请求头或凭据。使用实际的作业编号停止当前任务。
jobs
kill %1
使用下面的完整修复版处理程序替换调用方。带引号的分隔符会原样保留 JavaScript 内容。504 表示调用方的依赖服务超过截止时间;502 表示上游响应失败或协议不正确。成功请求仍会保留上游结果。elapsed_ms 表示墙上时钟测量的经过时间,不是 CPU 使用时间。日志和响应会共享同一个请求 ID,因此你可以跟踪单个请求在系统中的处理过程。
cat > src/index.js <<'JS'
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (url.pathname === '/health') return Response.json({status: 'ok'});
if (url.pathname !== '/api/check') return Response.json({error: 'not_found'}, {status: 404});
if (request.method !== 'GET') return Response.json({error: 'method_not_allowed'}, {status: 405});
const mode = url.searchParams.get('mode') || 'healthy';
if (!['healthy', 'slow', 'fail'].includes(mode)) {
return Response.json({error: 'invalid_mode'}, {status: 400});
}
const suppliedId = request.headers.get('X-Request-ID') || '';
const requestId = /^[a-z0-9-]{1,64}$/.test(suppliedId) ? suppliedId : crypto.randomUUID();
const headers = {'X-Request-ID': requestId, 'Cache-Control': 'no-store'};
const started = Date.now();
console.log(JSON.stringify({event: 'request_started', request_id: requestId, mode}));
const upstreamUrl = new URL('https://diagnostic.internal/check');
upstreamUrl.searchParams.set('mode', mode);
upstreamUrl.searchParams.set('probe', requestId);
const signal = AbortSignal.timeout(400);
const failure = (event, status, detail = {}) => {
console.error(JSON.stringify({event, request_id: requestId, mode, status,
elapsed_ms: Date.now() - started, ...detail}));
return Response.json({error: event, requestId}, {status, headers});
};
try {
const response = await env.UPSTREAM.fetch(upstreamUrl, {signal});
if (!response.ok) return failure('upstream_status', 502, {upstream_status: response.status});
const data = await response.json();
if (data.service !== 'labex-diagnostic-fixture' || data.status !== 'ok' || data.probe !== requestId) {
return failure('upstream_protocol', 502);
}
console.log(JSON.stringify({event: 'request_complete', request_id: requestId,
mode, status: 200, elapsed_ms: Date.now() - started}));
return Response.json({status: 'ok', requestId, upstream: data}, {headers});
} catch {
return signal.aborted ? failure('upstream_timeout', 504) : failure('upstream_exception', 502);
}
}
};
JS
npx wrangler dev -c wrangler.jsonc -c upstream/wrangler.jsonc --ip 0.0.0.0 --port 8080 > dev.log 2>&1 &
cat dev.log
显示 Ready 后,对比三种模式和未受影响的健康检查路由。每种失败都必须快速结束;slow 模式等待更长时间并不是修复方式。
curl -i --max-time 6 -H "X-Request-ID: healthy-two" "http://127.0.0.1:8080/api/check?mode=healthy"
curl -i --max-time 6 -H "X-Request-ID: slow-two" "http://127.0.0.1:8080/api/check?mode=slow"
curl -i --max-time 6 -H "X-Request-ID: fail-two" "http://127.0.0.1:8080/api/check?mode=fail"
curl -i http://127.0.0.1:8080/health
cat dev.log
healthy、slow、fail 三种模式应分别得到 200、504、502。每个响应都会在 JSON 和 X-Request-ID 中携带对应的请求 ID。日志会将 request_started 与 request_complete、upstream_timeout 或 upstream_status 配对。最后一种情况会单独记录上游的 503,与调用方返回的 502 区分开来。被捕获的故障仍可能对应成功的运行时结果,因为处理程序正常完成了执行,即使其 HTTP 状态是 504 或 502。
将上述结果与提供的执行限制示例进行比较:
cat evidence/execution-limit.json
该文件明确是用于教学的模拟证据,不是从你的 Worker 中捕获的结果。其中的 exceededCpu 结果表示执行限制导致的失败;运行时停止执行后,应用层的 catch 不一定还能运行。等待本实验中的异步上游服务,并不等同于持续消耗 CPU 时间。在考虑执行限制之前,应先调查计算量过大或请求处理量过大的问题;不要移除截止时间,也不要生成负载来模拟该示例。官方错误参考 介绍了异常和限制类别,运行时结果文档 说明了运行时结果与 HTTP 状态之间的区别。
运行验证。验证过程会启动一个使用独立测试装置的隔离运行时,使用独立的请求 ID,检查正常和失败响应契约,并确认模拟的 Authorization 请求头不会出现在捕获的应用日志中。学习者自己的日志文件不能作为独立证据。
验证实时请求、日志和指标
在本步骤中,使用你的学习账号验证修复后的行为。先停止本地开发进程,然后按照之前学习过的受限设备授权流程,让这台全新的虚拟机完成授权。
jobs
kill %1
npx wrangler login --device --browser=false --scopes account:read user:read workers_scripts:write workers_tail:read
在浏览器中打开命令输出的链接,输入代码,并批准使用指定的学习账号。确认 Wrangler 标准输出中的实际账号名称和 ID。
npx wrangler whoami --json
将 YOUR_ACCOUNT_ID 替换为实际的账号 ID。下面的 Node 命令会将该 ID 保存到两个项目配置中,使每次部署都明确指定资源归属。
node -e 'const fs=require("node:fs");for(const p of ["wrangler.jsonc","upstream/wrangler.jsonc"]){const c=JSON.parse(fs.readFileSync(p));c.account_id="YOUR_ACCOUNT_ID";fs.writeFileSync(p,JSON.stringify(c,null,2)+"\n");}'
cat wrangler.jsonc upstream/wrangler.jsonc
npx wrangler deploy -c upstream/wrangler.jsonc
npx wrangler deploy
测试装置没有公共端点。复制调用方实际的 workers.dev URL,并填入下面的变量。如果账号需要先注册子域名,请在继续之前按照「部署你的第一个 Cloudflare Worker」中的流程操作。
APP_URL="https://YOUR_WORKER.YOUR_SUBDOMAIN.workers.dev"
启动可读的实时日志流。发送请求前,等待 events.log 显示 Connected;仅仅存在该文件并不代表日志流已经准备就绪。
npx wrangler tail --format pretty > events.log 2> tail-errors.log &
cat events.log
curl -i --max-time 6 -H "X-Request-ID: cloud-healthy" "$APP_URL/api/check?mode=healthy"
curl -i --max-time 6 -H "X-Request-ID: cloud-slow" "$APP_URL/api/check?mode=slow"
curl -i --max-time 6 -H "X-Request-ID: cloud-fail" "$APP_URL/api/check?mode=fail"
cat events.log
在实时应用日志中查找预期的 200、504、502 响应及其匹配的请求 ID。如果事件暂时还没有到达,等待几秒后再次检查同一个日志文件;不要修改应用来人为制造事件。如果日志流已经结束,请停止操作并检查 tail-errors.log。可读日志可能会将被捕获的 504 调用标记为 Ok:这表示运行时已完成执行,并不表示上游服务运行正常。
在 Dashboard 中打开所选账号中的准确调用方,确认其 UPSTREAM 绑定指向这个测试装置,然后检查 Metrics。可用图表会聚合请求和调用错误,短时间测试后的数据可能存在延迟;记录实际显示的内容,不要要求立即出现非零总数。单个请求的证据应以实时日志和 HTTP 响应为准。被捕获的 504 可能会出现在 HTTP 响应状态数据中,但不会被计为未捕获的运行时异常。指标参考 介绍了聚合方式和调用类别。
在 Compute → Workers & Pages 中打开准确的调用方,然后选择 Metrics。检查 Worker 面包屑、已部署版本筛选器,以及覆盖请求时间的时间范围。刷新按钮位于时间范围选择器旁边。下面的截图是在发送模拟的正常、缓慢和上游失败请求后不久拍摄的;卡片仍显示 No data。这是对分析数据延迟的有效观察,并不表示没有请求运行,也不表示修复失败。你的名称、版本 ID 和总数会不同。不要仅为了匹配截图而生成额外负载。

在已完成授权的情况下运行验证。验证过程会查询资源归属和绑定状态,发送新的独立请求,并捕获单独的实时日志流。等待大约一分钟。如果日志流不可用或不完整,结果无法确定;检查连接是否准备就绪并重试,绝不能将缺少日志视为成功。验证通过后,使用当前的作业编号停止学习者日志流。
jobs
kill %1
删除诊断 Worker
在本步骤中,在仍处于授权状态时,仅删除本实验的调用方和测试装置。删除调用方之前,先检查两个名称及其所属账号。
cat wrangler.jsonc upstream/wrangler.jsonc
npx wrangler delete
npx wrangler delete -c upstream/wrangler.jsonc
在每个匹配的名称确认提示处按一次 y 键。固定版本的 CLI 在删除 Worker 后可能会报告旧版 KV 清理身份验证诊断信息;不要因此扩大权限,也不要认为任意错误都能证明删除失败。刷新 Dashboard 并运行验证。成功的已授权资源清单中必须找不到这两个名称。保留账号、子域名和无关资源。
断开虚拟机连接
在本步骤中,确认资源已删除后断开虚拟机连接。关闭终端或退出登录并不会删除云端资源。
npx wrangler logout
npx wrangler whoami --json
预期结果为 loggedIn=false;对于未授权状态,该命令可能以非零状态退出。运行最终检查。之后的全新实验仍可以重复使用你的浏览器登录状态和学习账号。
总结
你重现了未处理的超时,修复了有边界的依赖故障,并通过响应和结构化日志中的请求 ID 建立了关联。正常行为保持不变。你区分了应用层 HTTP 故障、运行时结果和模拟的 CPU 限制证据,验证了真实的云端行为,随后删除了两个 Worker 并断开了虚拟机连接。

