系列帖子
前一天,我刚完成 A Tour of Go 中文版生产环境 Playground 执行链路的一轮调整,让浏览器中的“运行”和“格式化”请求直接访问部署在 ZgoCloud 服务器上的 Playground 代理。
此前的部署和调整过程,可以参考:
ZgoCloud 服务器与 Playground 代理部署相关记录
A Tour of Go Playground 浏览器直连代理相关记录
调整完成以后,电脑端“运行”和“格式化”一直正常,手机端此前也做过真实测试并成功。
但第二天再次使用手机访问课程页面时,却出现了一个更麻烦的问题:
手机端的“运行”和“格式化”开始偶发失败,而且问题无法稳定复现。
相比持续失败,这种低频、间歇性的故障反而更难定位。
手机端首先出现运行失败
我最早是在微信中打开 A Tour of Go 中文版课程页面:
https://go-dev.shuijingwanwq.com/tour/welcome/1
点击“运行”以后,没有得到 Go 程序正常输出,而是出现:
Error communicating with remote server.
Error communicating with remote server.】这时候首先容易怀疑的是:
- 微信内置浏览器兼容性;
- 手机网络;
- Wi-Fi 与 5G 的差异;
- 浏览器直接访问 ZgoCloud 8443 端口失败;
- ZgoCloud 到官方 Playground 的代理异常。
但随后出现的情况,让这些判断都没有那么简单。
格式化也会偶发失败
同一台手机中,“格式化”同样出现过失败。
不过它显示的错误不是运行时的通信提示,而是:
[object Object]![【图 2:手机端点击“格式化”以后显示 [object Object]】](https://media.shuijingwanwq.com/2026/08/2-47-465x1024.png)
[object Object]】这至少说明两件事情。
第一,“运行”和“格式化”共同依赖的远程 Playground 请求链路值得重点检查。
第二,前端对于格式化失败的错误处理本身也不够友好。即使网络真的出现异常,最终用户也不应该直接看到 JavaScript 对象转换出来的 [object Object]。
不过在根因还没有确定之前,我暂时没有急着修改前端错误提示。
先把通信问题查清楚更重要。
奇怪的是,很快又恢复正常了
随后我重新测试微信,运行又恢复正常。
接着继续测试:
- 手机自带浏览器;
- QQ 浏览器;
- 百度 APP;
- Wi-Fi;
- 5G。
这些环境都出现过成功的情况。
例如后续再次修改示例代码,然后运行:
fmt.Println("Hello, 世界rrrrr")能够正常得到:
Hello, 世界rrrrr
这时候基本可以排除一种很简单的结论:
问题不是“微信一定失败”,也不是“某一个手机浏览器一定失败”。
同样也不能归结为:
Wi-Fi 失败,5G 正常或者:
5G 失败,Wi-Fi 正常两种网络环境都能够成功。
电脑端也做了额外验证。
由于这台电脑平时可能使用自己的网络服务,我一度怀疑电脑端之所以始终正常,是不是因为网络路径不同。
于是主动断开相关网络服务后重新测试。
结果电脑端“运行”和“格式化”仍然正常。
所以问题越来越像一种真正的间歇性网络或客户端通信异常。
开始从 ZgoCloud Nginx 日志寻找证据
既然浏览器直接请求 ZgoCloud Playground 代理,下一步就直接检查服务器日志。
当前生产请求主要是:
浏览器
→ play.go-dev.shuijingwanwq.com:8443
→ ZgoCloud Nginx
→ play.golang.org其中:
POST /compile负责运行代码,
POST /fmt负责格式化代码。
先统计当天所有相关请求,得到:
52 POST /compile -> 200
11 POST /fmt -> 200
5 OPTIONS /fmt -> 204
2 GET /compile -> 403
1 GET /fmt -> 403
/compile 和 /fmt 请求状态统计】这组结果很有意思。
真正来自浏览器正常业务流程的:
POST /compile
POST /fmt
OPTIONS /fmt全部成功。
其中:
52 次 POST /compile全部返回:
20011 次 POST /fmt也全部返回:
200CORS 预检:
5 次 OPTIONS /fmt全部是:
204剩下的 3 次 403 并不是实际用户运行代码产生的请求,而是 GoogleOther 使用 GET 方式访问 /compile 和 /fmt。
当前 Nginx 配置本来就只允许正常的 POST 和 OPTIONS 请求,所以这种 GET 返回 403 属于预期行为。
也就是说:
当天只要正常 Playground 请求真正进入了 Nginx access log,就没有发现一次 4xx 或 5xx 的实际业务失败。
但最早的失败请求甚至没有进入 Nginx
继续按照手机截图时间回查日志。
最早一次明显失败发生在大约 14:07。
检查 14:05 到 14:10 的 Playground access log,却只找到电脑 Firefox 在 14:05:50 发出的一次正常 /compile:
POST /compile?backend=
200没有找到手机端对应的 /compile 或 /fmt。
这意味着至少这一轮失败可能发生在:
手机
→ 建立到 play.go-dev.shuijingwanwq.com:8443 的连接
→ Nginx真正进入 Nginx 之前。
但事情还没有这么简单。
另一次前端失败附近,Nginx 却记录了 200
在大约 14:26 的另一轮测试中,百度 APP 页面显示过:
Error communicating with remote server.而服务器日志中同一时间附近却存在:
POST /compile?backend=
HTTP/2.0
200并且 User-Agent 明确来自百度 APP。
所以目前至少存在两种不同的现象:
第一种:
手机端显示失败
→ Nginx 没有对应请求以及:
第二种:
手机端显示失败
→ Nginx 附近却存在 HTTP 200 请求不过因为当前 access log 没有请求 ID,也没有更详细的上游耗时信息,所以还不能百分之百确认第二条 200 就一定对应页面上那一次失败操作。
这也是继续增强日志的原因。
不急着改代理架构,先提高可观测性
排查到这里,我没有马上:
- 把 8443 改成 443;
- 修改 CORS;
- 修改代理目标;
- 调整超时时间;
- 修改前端请求代码。
因为现在并没有证据证明这些配置本身存在稳定错误。
如果为了“解决问题”而直接修改生产架构,很可能只是把一个偶发问题变成另外一个问题。
当前真正欠缺的是:
下一次故障出现时,能够获得足够完整的证据。
原来的 Nginx combined 日志只能看到:
请求
状态码
响应大小
User-Agent但看不到一些非常关键的信息,例如:
$request_time
$upstream_response_time
$upstream_status
$upstream_addr幸运的是,这台 OneinStack 服务器的 nginx.conf 中本来就已经定义了 JSON 日志格式,其中已经包括:
request_time
upstream_time
upstream_host
upstream_status所以并不需要重新修改全局日志格式。
只需要给 Playground 的 8443 虚拟主机增加一份独立的详细日志。
为 Playground 增加独立调试日志
在不改变原有代理逻辑的情况下,增加:
/data/wwwlogs/play.go-dev.shuijingwanwq.com_debug.log同时增加 Playground 专用错误日志:
/data/wwwlogs/play.go-dev.shuijingwanwq.com_debug_error.log原来的:
/data/wwwlogs/play.go-dev.shuijingwanwq.com_nginx.log继续保留。
也就是说,这次调整只是增加观测能力,没有修改:
8443没有修改:
proxy_pass没有修改 CORS,也没有影响服务器上的其他网络服务。
修改完成后先执行 Nginx 配置检查:
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful随后重新加载 Nginx。
验证新的详细日志
最后主动发送一条测试请求:
package main
func main() {
println("DEBUG-OK")
}返回结果正常:
{"Errors":"","Events":[{"Message":"DEBUG-OK\n","Kind":"stderr","Delay":0}],"Status":0,"IsTest":false,"TestsFailed":0,"VetOK":true}新的详细日志同时记录:
"request_time":0.652
"status":"200"
"upstream_time":"0.652"
"upstream_host":"142.251.45.17:443"
"upstream_status":"200"
这意味着现在已经可以看到完整的代理执行结果。
这次测试中:
客户端
→ ZgoCloud Nginx
→ play.golang.org整个请求耗时:
0.652 秒上游响应耗时同样是:
0.652 秒官方 Playground 返回:
200ZgoCloud 最终返回客户端的状态同样是:
200专用错误日志没有产生异常记录。
这一次没有强行宣布“问题已修复”
这次排查最终没有得到一个能够稳定复现的根因。
如果只为了文章结尾好看,也完全可以挑一个最像的问题修改掉,然后写成“故障已经修复”。
但对于这种低频、间歇性的生产问题,我更愿意保留当前结论:
问题目前已经恢复正常,但还不能证明根因已经找到。
目前已经确认的事实包括:
- 手机端确实出现过“运行”失败;
- 手机端也出现过“格式化”失败;
- 微信、自带浏览器、QQ 浏览器和百度 APP 后续都能够正常运行;
- Wi-Fi 与 5G 都能够成功;
- 电脑无论是否使用原来的网络服务都可以正常运行;
- 当天进入 Nginx 的正常
/compile和/fmt请求全部返回成功; - 一次早期手机失败没有对应的 Nginx access log;
- 另一轮失败附近存在手机请求返回 200 的记录;
- 当前还没有足够证据把问题归因于浏览器、8443、CORS、ZgoCloud 或官方 Playground 中的任何一层。
因此这次最重要的成果不是“修改了多少代码”,而是把生产环境从:
出了问题以后只能猜推进到了:
下一次问题出现时,可以直接看到请求和上游链路的详细证据后续
接下来不再继续高频人工点击测试。
让真实用户访问和日常使用自然产生请求即可。
如果以后再次出现“运行”或者“格式化”失败,只需要记录故障发生时间,再检查新的 Playground 专用日志,就可以同时确认:
请求是否进入 Nginx最终 HTTP 状态上游实际状态完整请求耗时上游响应耗时实际连接到哪个上游地址如果下一次能够捕获到真正的异常请求,才有足够依据决定是否需要进一步调整:
端口
代理
TLS
CORS
超时
前端请求逻辑对于这种偶发故障,与其凭一次截图快速修改生产环境,不如先让系统具备回答问题的能力。
这一次先把“为什么失败”变成下一次可以通过日志直接验证的问题,再等待真正的异常样本出现。
A Tour of Go 多语言翻译项目
本系列完整记录 A Tour of Go 多语言翻译项目从架构设计、整页翻译、结构保护、自动校验,到生产发布与后续维护的实际开发过程。
项目入口:
✅ 在线学习:A Tour of Go 简体中文版
✅ 项目源码:GitHub:shuijingwan/go-tour-i18n
当前第一阶段已完成简体中文版本。项目属于非官方社区多语言翻译项目,与 Go 官方无隶属或授权关系。


发表回复