年前游戏做阿语的版本移植,本以为和其他语言版本一样,简单的做好翻译与UI适配即可迅速发布。结果将测试包发给沙特的测试人员后,反馈无法登录游戏。
无法登陆的bug在前面坐其他语言版本的时候也经常遇到,其原因不外乎后端代码未同步、配置出错、链接的URL已被废弃等等。迅速将上述可能的原因检查一遍之后,在本地常规测试通过,然后将新包发送给沙特的测试人员。由于时差关系,反馈要等到第二天。
然而,问题已然存在!
这时,后端日志发现,根本没有该测试人员的登录请求信息。也即是说,请求没有抵达后端。
BUG还原
由于本地测试完全OK,在北京的合作伙伴也能测试通过,使得所有的分析都需要远在沙特的测试人员提供。尴尬的是,这位测试实际上只是一名普通玩家,不是专业测试人员,所以无法获取专业的反馈。再者,因为时差关系,无法即时通讯。因此,我们只有先猜测原因,然后引导沙特玩家验证。同时,我们让运营寻找更多的玩家帮忙测试。几经来回,收集到很多信息,事后来看,有些信息完全就是错误的干扰信息。
获取到的玩家基本信息:沙特阿拉伯、三星S6、Android 6.0、wifi数据网络都畅通、VPN畅通。
游戏包信息:targetversion 23、各语言版本仅仅部分配置不一致、内网环境测试OK、外网环境测试OK
其他信息:
- 阿语地区,约旦沙特不能登录,伊拉克、埃及、土耳其可以登录
- 沙特约旦新政策,赌博类游戏全部下架
- 游戏老版本在沙特可以登录正常游戏(新版与老版差异太大)
- 在加拿大的阿语玩家可以登录
- 沙特玩家另一部android 4.3的手机可以登录
- 沙特玩家使用另一个越南语言版本的包,也不能登录。
排查
针对网络请求未到达,我们分解成三个步骤:1)请求未发出,2)请求被墙,未到nginx,3)请求到了nginx,但被nginx丢弃。
由于其他语言版本已经上线稳定运行,而沙特刚好禁赌,我们怀疑是否链接被墙,于是联系运维检查海外的服务器,检查nginx的相关配置,检查是否有玩家的请求信息。但运维提供的信息非常有限,都没法明确回答“是否接收到玩家的请求?”这个问题。后来玩家说,以前的版本可以登录,我们排除上述2的可能。根据nginx不会丢弃请求来排除3的可能。
最终问题回到客户端本身。到底是什么原因造成请求发不出去呢?为什么同一个架构下,只有沙特玩家使用android6.0手机登录不进去我们的游戏呢?
首先我们想到的是android6.0的特殊性,6.0的android升级了权限管理、废弃了老式的httpclient。这两个change与我们的bug看起来都有联系。
针对权限系统,我们降低了targetversion,降低了sdk版本。没用
针对httpclient,我们使用的是okhttp啊。排除
然后只剩下 玩家在沙特 这个条件了。于是我们在与网路请求有关的每一个步骤,都输出文件日志。最后让玩家将该文件发回来(教会玩家找到日志文件也是个苦力活)。
原来如此
第二天收到发回来的日志文件时,文件名上的阿语让我愣了一下,我记得日志的文件名是用的当地日期时间生成的,为什么会有阿拉伯语呢?难道。。。
打开日志文件,一路跟随,在请求的时候发现,请求URL为空!
幸好打印的日志信息足够多,前后对比,终于发现问题所在。
我们的游戏和新近大多数手游一样,采用的是lua + c++引擎 + 部分原生代码。在网络请求这一块,直接使用的原生实现,也就是android端用的okhttp,没有在引擎层用C++封装也没有JNI。所以请求的数据信息全部是lua逻辑层传过来的,包括请求地址,请求数据。数据在lua与原生(android的java,ios的oc)之间是通过字典(key-value)传递的。两方使用同一种规则构建一个key,一方存一方取。
对于网络http请求,每一个请求都有唯一的数字ID,双方约定的key格式是http_request_%d
,lua这边用string.format
,java用String.format
。问题就出在格式化这个数字ID上,lua不会自动本地化,无论在哪个地区,传递的数字ID都是 1、2、3这种,相应存下的key就是 http_request_1
,而在java端用format取的时候,如果没有指定locale,则会根据系统语言自动决定,在我们已发行的语言版本里,数字都是默认使用1、2、3这种所谓的阿拉伯数字。而真正的阿拉伯语言里,使用的数字是١、٢、٣(注意阿语是从右往左的,1对应١)。于是,java端format之后出来的key变成了http_request_١
,自然是找不到对应的value,当然也就请求不出去了。下面给一张阿语数字的对照表
填坑
既然知晓了原因,解决办法也是多样。我们简单的在java里用了String.format数字的地方,指定locale为us,统一格式不受系统语言影响。
事后反思这个bug的跟踪过程,我们总结了不少。在代码层面、远程调试、各部门协作等方面都有思考与待优化的空间。
我本人也陷入了经验不足与思维盲区,比如最开始我就想到是否是系统语言的原因,尝试过将测试机刷成沙特地区的系统(未成功),却没想过直接将系统语言直接切换成阿语,这样就能本地重现bug,想起来真是尴尬。
再有就是针对线上的远程调试,我们考虑在一些关键的基础设施代码里写点本地日志,然后通过后端控制开启(与玩家协商)与否来决定是否日志上传。这样,以后发生类似线上bug,可以在取得玩家同意后,将玩家的本地日志上传至服务器(当然对于这个直接连不上服务器的bug没用)。这样可以避免玩家直接描述里附带的部分干扰信息(比如玩家说android4.0可以登录,让我们查了半天的6.0 changelist)。
还有就是部门之间的协同上面,效率真的太低了。大家时间都很宝贵的,如果交流这么困难,谁还愿意浪费时间与口水和你合作!