线上脚本出了 bug,最贵的不是修复成本,而是"不知道哪一行错的"。Lua 的 debug 库就是为这件事准备的:它能在运行时拿到调用栈、函数信息、甚至局部变量的值。996 引擎的 Lua 报错默认会打一行错误消息,但业务复杂之后,你需要自己把完整的栈带回来看。
最常用的组合是 xpcall + debug.traceback:普通 pcall 只返回"失败了"和一句错误字符串,而 xpcall 的第二个参数是错误处理器,在错误发生的瞬间被调用,此时 debug.traceback 能拿到完整调用链:
local function mainLogic()
local t = nil
return t.field -- 这里会炸
end
local ok, err = xpcall(mainLogic, function(e)
return e .. "\n" .. debug.traceback("stack:", 2)
end)
if not ok then
release_print("LUA ERROR:\n" .. err)
-- 这里可以把 err 上报到日志文件或 GM 频道
end
debug.getinfo 能查任意函数的元信息:定义在哪个文件、第几行、有几个 upvalue。排查"这个函数到底被谁改过"时特别有用——热更冲突经常表现为同名函数来源不明,debug.getinfo(f, "S").source 一打印就现形。
debug.getlocal / setlocal 在错误处理器里可以枚举出错那一帧的局部变量名和值。配合上面的 traceback,等于案发现场的物证清单:玩家 id 是多少、参数传成了什么,一目了然。
debug.sethook 可以设置调用钩子,在每行/每次函数调用时触发回调。它是性能剖析器和沙盒限制器的基础:统计每个函数被命中的次数就能找出热点函数;给钩子里加执行计数,超限就 error 掉,可以兜住第三方脚本的死循环。
把这套东西封装成全站统一的 SafeCall(函数, ...):内部 xpcall + traceback + 把错误串按"时间 + 玩家名 + 栈"写入日志文件,关键业务(充值回调、装备操作)全部走它。上线后每次玩家反馈异常,先翻日志再动手,定位速度完全不同。
两个注意点:debug 库本身有性能开销,traceback 只在错误路径上调用,不要放进高频循环;另外线上环境慎用 sethook 的"每行"模式,钩子触发太密集会拖慢整个 M2,做剖析时临时开、采完样立刻移除。
本文由 996 技术组基于 996 引擎官方知识库与浮生梦老师课程体系整理。团队长期从事传奇类引擎 Lua 后端逻辑、客户端界面与商业版本交付,内容以官方知识库与真实项目为出处,按版本持续修订。
senddiymsg(play, jsonStr)
日志上报接口,错误栈还原后的第一件事是把结构化结果报上来
senddiymsg(play,jsonStr)
release_print(msg)
打印消息到控制台,开发期看完整栈,函数名与行号都在这里
logact(play, logAct, loginfo, nParam1, nParam2, nParam3, nParam4, nParam5)
自定义日志,线上留一份关键字段,栈太长的场景按需截断
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
设计初衷 新手的第一个 30 级决定留存:被红名追杀一次、被高级玩家抢一次 BOSS,新号当周流失率直接翻倍。新手走廊是 1…
设计初衷 拍卖行的恶意挂拍有两种:挂天价试探市场、批量挂垃圾物刷屏——挂拍零成本,恶意就没有代价。保证金机制让挂拍押上真金白…
设计初衷 家园系统回答的是长线玩家"下线之后游戏还在为我工作"的情感需求:种下去的种子在离线时生长,上线收割即是回馈。产出设…
底层原理 缓存防的是重复查询,但有一类查询永远查不到:恶意玩家批量请求不存在的物品名,每次都绕过缓存直击底层查询——缓存对"…
底层原理 金币、功勋这类关键数值,单账本一旦被静默改坏(bug 写错、内存篡改),没有任何报警——账本自己不会喊冤。双写校验…
底层原理 内存涨了不回落,十有八九是有张该死的表被谁攥着不放。Lua 的回收按可达性判定:只要还存在一条从全局表、活跃闭包或…